Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
}
private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're builidng.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
/**
* The InputId of the current node.
*/
private InputId inputId;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n,
Node parent) {
inputId = t.getInputId();
if (n.isFunction() ||
n.isScript()) {
Preconditions.checkNotNull(inputId);
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child, n);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
attachLiteralTypes(t, n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n, parent);
checkForCallingConventionDefiningCalls(n, delegateCallingConventions);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n, parent);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.isGetProp() &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n, parent);
break;
case Token.VAR:
defineVar(n, parent);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.isExprResult() &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
}
private void attachLiteralTypes(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
// Defer keys to the Token.OBJECTLIT case
if (!NodeUtil.isObjectLitKey(n, n.getParent())) {
n.setJSType(getNativeType(STRING_TYPE));
}
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.OBJECTLIT:
defineObjectLiteral(t, n);
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void defineObjectLiteral(NodeTraversal t, Node objectLit) {
// Handle the @
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Type.toMaybeEnumType().getElementsType() :
NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType);
if (keyType != null) {
// Try to declare this property in the current scope if it
// has an authoritative name.
String qualifiedName = NodeUtil.getBestLValueName(keyNode);
if (qualifiedName != null) {
defineSlot(keyNode, objLit, qualifiedName, keyType, false);
} else {
setDeferredType(keyNode, keyType);
}
if (objLitType != null && declareOnOwner) {
// Declare this property on its object literal.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
objLitType.defineDeclaredProperty(memberName, keyType, keyNode);
}
}
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
private JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode =
node.isGetProp() ? node.getFirstChild() :
NodeUtil.isObjectLitKey(node, node.getParent()) ? node.getParent() :
null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
jsType = createFunctionTypeFromNodes(
null, fnName, info, node);
}
}
return jsType;
}
/**
* Asserts that it's ok to define this node's name.
* The node should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
void defineCatch(Node n, Node parent) {
assertDefinitionNode(n, Token.CATCH);
Node catchName = n.getFirstChild();
defineSlot(catchName, n, null);
}
/**
* Defines a VAR initialization.
*/
void defineVar(Node n, Node parent) {
assertDefinitionNode(n, Token.VAR);
JSDocInfo info = n.getJSDocInfo();
if (n.hasMoreThanOneChild()) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name :
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSDocInfo());
}
}
/**
* Defines a function literal.
*/
void defineFunctionLiteral(Node n, Node parent) {
assertDefinitionNode(n, Token.FUNCTION);
// Determine the name and JSDocInfo and lvalue for the function.
// Any of these may be null.
Node lValue = NodeUtil.getBestLValue(n);
JSDocInfo info = NodeUtil.getBestJSDocInfo(n);
String functionName = NodeUtil.getBestLValueName(lValue);
FunctionType functionType =
createFunctionTypeFromNodes(n, functionName, info, lValue);
// Assigning the function type to the function node
setDeferredType(n, functionType);
// Declare this symbol in the current scope iff it's a function
// declaration. Otherwise, the declaration will happen in other
// code paths.
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
* @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param parent {@code var}'s parent.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, Node parent, JSDocInfo info) {
Node value = name.getFirstChild();
// variable's type
JSType type = getDeclaredType(sourceName, info, name, value);
if (type == null) {
// The variable's type will be inferred.
type = name.isFromExterns() ?
getNativeType(UNKNOWN_TYPE) : null;
}
defineSlot(name, var, type);
}
/**
* If a variable is assigned a function literal in the global scope,
* make that a declared type (even if there's no doc info).
* There's only one exception to this rule:
* if the return type is inferred, and we're in a local
* scope, we should assume the whole function is inferred.
*/
private boolean shouldUseFunctionLiteralType(
FunctionType type, JSDocInfo info, Node lValue) {
if (info != null) {
return true;
}
if (lValue != null &&
NodeUtil.isObjectLitKey(lValue, lValue.getParent())) {
return false;
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualfied names.
// Object literal keys will have to compute their names themselves.
if (n.isName()) {
Preconditions.checkArgument(
parent.isFunction() ||
parent.isVar() ||
parent.isParamList() ||
parent.isCatch());
} else {
Preconditions.checkArgument(
n.isGetProp() &&
(parent.isAssign() ||
parent.isExprResult()));
}
defineSlot(n, parent, n.getQualifiedName(), type, inferred);
}
/**
* Defines a symbol in the current scope.
*
* @param n the defining NAME or GETPROP or object literal key node.
* @param parent the {@code n}'s parent.
* @param variableName The name that this should be known by.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
* @param inferred Whether the type is inferred or declared.
*/
void defineSlot(Node n, Node parent, String variableName,
JSType type, boolean inferred) {
Preconditions.checkArgument(!variableName.isEmpty());
boolean isGlobalVar = n.isName() && scope.isGlobal();
boolean shouldDeclareOnGlobalThis =
isGlobalVar &&
(parent.isVar() ||
parent.isFunction());
// If n is a property, then we should really declare it in the
// scope where the root object appears. This helps out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.isGetProp() && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// declared in closest scope?
CompilerInput input = compiler.getInput(inputId);
if (scopeToDeclareIn.isDeclared(
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
validator.expectUndeclaredVariable(
sourceName, input, n, parent, oldVar, variableName, type);
} else {
if (!inferred) {
setDeferredType(n, type);
}
// The input may be null if we are working with a AST snippet.
boolean isExtern = n.isFromExterns();
Var newVar =
scopeToDeclareIn.declare(variableName, n, type, input, inferred);
if (type instanceof EnumType) {
Node initialValue = newVar.getInitialValue();
boolean isValidValue = initialValue != null &&
(initialValue.isObjectLit() ||
initialValue.isQualifiedName());
if (!isValidValue) {
compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER));
}
}
// We need to do some additional work for constructors and interfaces.
FunctionType fnType = JSType.toMaybeFunctionType(type);
if (fnType != null &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
if ((fnType.isConstructor() || fnType.isInterface()) &&
!fnType.equals(getNativeType(U2U_CONSTRUCTOR_TYPE))) {
// Declare var.prototype in the scope chain.
FunctionType superClassCtor = fnType.getSuperClassConstructor();
ObjectType.Property prototypeSlot = fnType.getSlot("prototype");
String prototypeName = variableName + ".prototype";
// There are some rare cases where the prototype will already
// be declared. See TypedScopeCreatorTest#testBogusPrototypeInit.
// Fortunately, other warnings will complain if this happens.
if (scopeToDeclareIn.getOwnSlot(prototypeName) == null) {
// When we declare the function prototype implicitly, we
// want to make sure that the function and its prototype
// are declared at the same node. We also want to make sure
// that the if a symbol has both a Var and a JSType, they have
// the same node.
//
// This consistency is helpful to users of SymbolTable,
// because everything gets declared at the same place.
prototypeSlot.setNode(n);
scopeToDeclareIn.declare(prototypeName,
n, prototypeSlot.getType(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().equals(
getNativeType(OBJECT_TYPE)));
}
// Make sure the variable is initialized to something if
// it constructs itself.
if (newVar.getInitialValue() == null &&
!isExtern &&
// We want to make sure that when we declare a new instance
// type (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> constructors
// (like function(new:Array). Checking the constructed
// type against the variable name is a sufficient check for
// this.
variableName.equals(
fnType.getInstanceType().getReferenceName())) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
}
}
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(GLOBAL_THIS);
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
n);
} else {
globalThis.defineDeclaredProperty(variableName, type, n);
}
}
if (isGlobalVar && "Window".equals(variableName)
&& type != null
&& type.isFunctionType()
&& type.isConstructor()) {
FunctionType globalThisCtor =
typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor();
globalThisCtor.getInstanceType().clearCachedValues();
globalThisCtor.getPrototype().clearCachedValues();
globalThisCtor
.setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType());
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Scope scope = getQnameRootScope(n);
return scope != null && scope.isGlobal();
}
/**
* Return the scope for the name of the given node.
*/
private Scope getQnameRootScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(n);
if (root.isName()) {
Var var = scope.getVar(root.getString());
if (var != null) {
return var.getScope();
}
}
return null;
}
/**
* Look for a type declaration on a property assignment
* (in an ASSIGN or an object literal key).
*
* @param info The doc info for this property.
* @param lValue The l-value node.
* @param rValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredType(String sourceName, JSDocInfo info,
Node lValue, @Nullable Node rValue) {
if (info != null && info.hasType()) {
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
} else if (rValue != null && rValue.isFunction() &&
shouldUseFunctionLiteralType(
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
if (rValue.getJSType() != null
&& !rValue.getJSType().isUnknownType()) {
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
&& firstClause.getString().equals(lValue.getString());
if (namesMatch && secondClause.getJSType() != null
&& !secondClause.getJSType().isUnknownType()) {
return secondClause.getJSType();
}
}
}
}
}
}
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
}
private FunctionType getFunctionType(@Nullable Var v) {
JSType t = v == null ? null : v.getType();
ObjectType o = t == null ? null : t.dereference();
return JSType.toMaybeFunctionType(o);
}
/**
* Look for calls that set a delegate method's calling convention.
*/
private void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
codingConvention.checkForCallingConventionDefiningCalls(n,
delegateCallingConventions);
}
/**
* Look for class-defining calls.
* Because JS has no 'native' syntax for defining classes,
* this is often very coding-convention dependent and business-logic heavy.
*/
private void checkForClassDefiningCalls(
NodeTraversal t, Node n, Node parent) {
SubclassRelationship relationship =
codingConvention.getClassesDefinedByCall(n);
if (relationship != null) {
FunctionType superCtor = getFunctionType(
scope.getVar(relationship.superclassName));
FunctionType subCtor = getFunctionType(
scope.getVar(relationship.subclassName));
if (superCtor != null && superCtor.isConstructor() &&
subCtor !=
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> null && subCtor.isConstructor()) {
ObjectType superClass = superCtor.getInstanceType();
ObjectType subClass = subCtor.getInstanceType();
// superCtor and subCtor might be structural constructors
// (like {function(new:Object)}) so we need to resolve them back
// to the original ctor objects.
superCtor = superClass.getConstructor();
subCtor = subClass.getConstructor();
if (relationship.type == SubclassType.INHERITS &&
!superClass.isEmptyType() && !subClass.isEmptyType()) {
validator.expectSuperType(t, n, superClass, subClass);
}
if (superCtor != null && subCtor != null) {
codingConvention.applySubclassRelationship(
superCtor, subCtor, relationship.type);
}
}
}
String singletonGetterClassName =
codingConvention.getSingletonGetterClassName(n);
if (singletonGetterClassName != null) {
ObjectType objectType = ObjectType.cast(
typeRegistry.getType(singletonGetterClassName));
if (objectType != null) {
FunctionType functionType = objectType.getConstructor();
if (functionType != null) {
FunctionType getterType =
typeRegistry.createFunctionType(objectType);
codingConvention.applySingletonGetter(functionType, getterType,
objectType);
}
}
}
DelegateRelationship delegateRelationship =
codingConvention.getDelegateRelationship(n);
if (delegateRelationship != null) {
applyDelegateRelationship(delegateRelationship);
}
ObjectLiteralCast objectLiteralCast =
codingConvention.getObjectLiteralCast(t, n);
if (objectLiteralCast != null) {
ObjectType type = ObjectType.cast(
typeRegistry.getType(objectLiteralCast.typeName));
if (type != null && type.getConstructor() != null) {
setDeferredType(objectLiteralCast.objectNode, type);
} else {
compiler.report(JSError.make(t.getSourceName(), n,
CONSTRUCTOR_EXPECTED));
}
}
}
/**
* Apply special properties that only apply to delegates.
*/
private void applyDelegateRelationship(
DelegateRelationship delegateRelationship) {
ObjectType delegatorObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegator));
ObjectType delegateBaseObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegateBase));
ObjectType delegateSuperObject = ObjectType.cast(
typeRegistry.getType(codingConvention.getDelegateSuperclassName()));
if (delegatorObject != null &&
delegateBaseObject != null &&
delegateSuperObject != null) {
FunctionType delegatorCtor = delegatorObject.getConstructor();
FunctionType delegateBaseCtor = delegateBaseObject.getConstructor();
FunctionType delegateSuperCtor = delegateSuperObject.getConstructor();
if (delegatorCtor != null && delegateBaseCtor != null &&
delegateSuperCtor != null) {
FunctionParamBuilder functionParamBuilder =
new FunctionParamBuilder(typeRegistry
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>);
functionParamBuilder.addRequiredParams(
getNativeType(U2U_CONSTRUCTOR_TYPE));
FunctionType findDelegate = typeRegistry.createFunctionType(
typeRegistry.createDefaultObjectUnion(delegateBaseObject),
functionParamBuilder.build());
FunctionType delegateProxy = typeRegistry.createConstructorType(
delegateBaseObject.getReferenceName() + DELEGATE_PROXY_SUFFIX,
null, null, null);
delegateProxy.setPrototypeBasedOn(delegateBaseObject);
codingConvention.applyDelegateRelationship(
delegateSuperObject, delegateBaseObject, delegatorObject,
delegateProxy, findDelegate);
delegateProxyPrototypes.add(delegateProxy.getPrototype());
}
}
}
/**
* Declare the symbol for a qualified name in the global scope.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param parent The parent of {@code n}.
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
Node ownerNode = n.getFirstChild();
String ownerName = ownerNode.getQualifiedName();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
Preconditions.checkArgument(qName != null && ownerName != null);
// Precedence of type information on GETPROPs:
// 1) @type annnotation / @enum annotation
// 2) ASSIGN to FUNCTION literal
// 3) @param/@return annotation (with no function literal)
// 4) ASSIGN to something marked @const
// 5) ASSIGN to anything else
//
// 1, 3, and 4 are declarations, 5 is inferred, and 2 is a declaration iff
// the function has jsdoc or has not been declared before.
//
// FUNCTION literals are special because TypedScopeCreator is very smart
// about getting as much type information as possible for them.
// Determining type for #1 + #2 + #3 + #4
JSType valueType = getDeclaredType(t.getSourceName(), info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #5
valueType = rhsValue.getJSType();
}
// Function prototypes are special.
// It's a common JS idiom to do:
// F.prototype = { ... };
// So if F does not have an explicitly declared super type,
// allow F.prototype to be redefined arbitrarily.
if ("prototype".equals(propName)) {
Var qVar = scope.getVar(qName);
if
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> (qVar != null) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to an object literal,
// then they are responsible for making sure that the object literal's
// implicit prototype is set up appropriately. We just obey
// the @extends tag.
ObjectType qVarType = ObjectType.cast(qVar.getType());
if (qVarType != null &&
rhsValue != null &&
rhsValue.isObjectLit()) {
typeRegistry.resetImplicitPrototype(
rhsValue.getJSType(), qVarType.getImplicitPrototype());
} else if (!qVar.isTypeInferred()) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to some arbitrary expression,
// there's not much we can do. We just ignore the expression,
// and hope they've annotated their code in a way to tell us
// what props are going to be on that prototype.
return;
}
if (qVar.getScope() == scope) {
scope.undeclare(qVar);
}
}
}
if (valueType == null) {
if (parent.isExprResult()) {
stubDeclarations.add(new StubDeclaration(
n,
t.getInput() != null && t.getInput().isExtern(),
ownerName));
}
return;
}
// NOTE(nicksantos): Determining whether a property is declared or not
// is really really obnoxious.
//
// The problem is that there are two (equally valid) coding styles:
//
// (function() {
// /* The authoritative definition of goog.bar. */
// goog.bar = function() {};
// })();
//
// function f() {
// goog.bar();
// /* Reset goog.bar to a no-op. */
// goog.bar = function() {};
// }
ownerType.defineDeclaredProperty(propName, valueType, n);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
defineSlot(n, parent, valueType, inferred);
} else if (rhsValue != null && rhsValue.isTrue()) {
// We declare these for delegate proxy method properties.
FunctionType ownerType =
JSType.toMaybeFunctionType(getObjectSlot(ownerName));
if (ownerType != null) {
JSType ownerTypeOfThis = ownerType.getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true);
}
}
}
}
/**
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> * Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub delcarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
ObjectType unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, n);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final ObjectType thisType;
CollectProperties(ObjectType thisType) {
this.thisType = thisType;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isExprResult()) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(t, child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(t, child, child, null);
break;
}
}
}
private void maybeCollectMember(NodeTraversal t,
Node member, Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no J
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>SDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (info == null ||
member.getType() != Token.GETPROP ||
member.getFirstChild().getType() != Token.THIS) {
return;
}
member.getFirstChild().setJSType(thisType);
JSType jsType = getDeclaredType(t.getSourceName(), info, member, value);
Node name = member.getLastChild();
if (jsType != null &&
(name.isName() || name.isString())) {
thisType.defineDeclaredProperty(
name.getString(),
jsType,
member);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a ter
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>rible, terrible hack
// to bail out on recusive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.isGetProp()) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're builidng.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents != null) {
for (String varName : contents.getEscapedVarNames()) {
Var v = scope.getVar(varName);
Preconditions.checkState(v.getScope() == scope);
v.markEscaped();
}
}
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) return;
if (n.isParamList() && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.isEmpty()) {
Scope.Var fnVar = scope.getVar(fnName);
if (fnVar == null ||
// Make sure we're not touching a native function. Native
// functions aren't bleeding, but may not have a declaration
// node.
(fnVar.getNameNode() != null &&
// Make sure that the function is actually bleeding by checking
// if has already been declared.
fnVar.getInitialValue() != fnNode)) {
define
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Slot(fnNameNode, fnNode, fnNode.getJSType(), false);
}
}
declareArguments(fnNode);
}
/**
* Declares all of a function's arguments.
*/
private void declareArguments(Node functionNode) {
Node astParameters = functionNode.getFirstChild().getNext();
Node body = astParameters.getNext();
FunctionType functionType =
JSType.toMaybeFunctionType(functionNode.getJSType());
if (functionType != null) {
Node jsDocParameters = functionType.getParametersNode();
if (jsDocParameters != null) {
Node jsDocParameter = jsDocParameters.getFirstChild();
for (Node astParameter : astParameters.children()) {
if (jsDocParameter != null) {
defineSlot(astParameter, functionNode,
jsDocParameter.getJSType(), false);
jsDocParameter = jsDocParameter.getNext();
} else {
defineSlot(astParameter, functionNode, null, true);
}
}
}
}
} // end declareArguments
} // end LocalScopeBuilder
/**
* Does a first-order function analysis that just looks at simple things
* like what variables are escaped, and whether 'this' is used.
*/
private static class FirstOrderFunctionAnalyzer
extends AbstractScopedCallback implements CompilerPass {
private final AbstractCompiler compiler;
private final Map<Node, AstFunctionContents> data;
FirstOrderFunctionAnalyzer(
AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) {
this.compiler = compiler;
this.data = outParam;
}
@Override public void process(Node externs, Node root) {
if (externs == null) {
NodeTraversal.traverse(compiler, root, this);
} else {
NodeTraversal.traverseRoots(
compiler, ImmutableList.of(externs, root), this);
}
}
@Override public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
data.put(n, new AstFunctionContents(n));
}
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (n.isReturn() && n.getFirstChild() != null) {
data.get(t.getScopeRoot()).recordNonEmptyReturn();
} else if (n.isName() && NodeUtil.isLValue(n)) {
String name = n.getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordEscapedVarName(name);
}
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
}
private AstFunctionContents getFunctionAnalysisResults(@Nullable Node n) {
if (n == null) {
return null;
}
// Sometimes this will return null in things like
// NameReferenceGraphConstruction that build partial scopes.
return functionAnalysisResults.get(n);
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.*;
/**
* Verifies that constants are only assigned a value once.
* e.g. var XX = 5;
* XX = 3; // error!
* XX++; // error!
*
*/
class ConstCheck extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR =
DiagnosticType.error(
"JSC_CONSTANT_REASSIGNED_VALUE_ERROR",
"constant {0} assigned a value more than once");
private final AbstractCompiler compiler;
private final Set<Scope.Var> initializedConstants;
/**
* Creates an instance.
*/
public ConstCheck(AbstractCompiler compiler) {
this.compiler = compiler;
this.initializedConstants = new HashSet<Scope.Var>();
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (parent != null &&
parent.isVar() &&
n.hasChildren()) {
String name = n.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD: {
Node lhs = n.getFirstChild();
if (lhs.isName()) {
String name = lhs.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
}
case Token.INC:
case Token.DEC: {
Node lhs = n.getFirstChild();
if (lhs.isName()) {
String name = lhs.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
reportError(t, n, name);
}
}
break;
}
}
}
/**
* Gets whether a variable is a constant initialized to a literal value at
* the point where it is declared.
*/
private boolean isConstant(Scope.Var var) {
return var != null && var.isConst();
}
/**
* Reports a reassigned constant error.
*/
void reportError(NodeTraversal t, Node n, String name) {
compiler.report(t.makeError(n, CONST_REASSIGNED_VALUE_ERROR, name));
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>name, true);
}
@Override
public boolean isPrivate(String name) {
return nextConvention.isPrivate(name);
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return nextConvention.getClassesDefinedByCall(callNode);
}
@Override
public boolean isSuperClassReference(String propertyName) {
return nextConvention.isSuperClassReference(propertyName);
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
return nextConvention.extractClassNameIfProvide(node, parent);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
return nextConvention.extractClassNameIfRequire(node, parent);
}
@Override
public String getExportPropertyFunction() {
return nextConvention.getExportPropertyFunction();
}
@Override
public String getExportSymbolFunction() {
return nextConvention.getExportSymbolFunction();
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return nextConvention.identifyTypeDeclarationCall(n);
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
nextConvention.applySubclassRelationship(
parentCtor, childCtor, type);
}
@Override
public String getAbstractMethodName() {
return nextConvention.getAbstractMethodName();
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return nextConvention.getSingletonGetterClassName(callNode);
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
nextConvention.applySingletonGetter(
functionType, getterType, objectType);
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return nextConvention.getDelegateRelationship(callNode);
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
nextConvention.applyDelegateRelationship(
delegateSuperclass, delegateBase, delegator,
delegateProxy, findDelegate);
}
@Override
public String getDelegateSuperclassName() {
return nextConvention.getDelegateSuperclassName();
}
@Override
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
nextConvention.checkForCallingConventionDefiningCalls(
n, delegateCallingConventions);
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, Scope scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
nextConvention.defineDelegateProxyPrototypeProperties(
registry, scope, delegateProxyPrototypes, delegateCallingConventions);
}
@Override
public String getGlobalObject() {
return next
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Convention.getGlobalObject();
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return nextConvention.getAssertionFunctions();
}
@Override
public Bind describeFunctionBind(Node n) {
return nextConvention.describeFunctionBind(n);
}
@Override
public boolean isPropertyTestFunction(Node call) {
return nextConvention.isPropertyTestFunction(call);
}
@Override
public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t,
Node callNode) {
return nextConvention.getObjectLiteralCast(t, callNode);
}
}
/**
* The default coding convention.
* Should be at the bottom of all proxy chains.
*/
private static class DefaultCodingConvention implements CodingConvention {
private static final long serialVersionUID = 1L;
@Override
public boolean isConstant(String variableName) {
return false;
}
@Override
public boolean isConstantKey(String variableName) {
return false;
}
@Override
public boolean isValidEnumKey(String key) {
return key != null && key.length() > 0;
}
@Override
public boolean isOptionalParameter(Node parameter) {
// be as lax as possible, but this must be mutually exclusive from
// var_args parameters.
return !isVarArgsParameter(parameter);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
// be as lax as possible
return parameter.getParent().getLastChild() == parameter;
}
@Override
public boolean isExported(String name, boolean local) {
return local && name.startsWith("$super");
}
@Override
public boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return false;
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String getExportPropertyFunction() {
return null;
}
@Override
public String getExportSymbolFunction() {
return null;
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return null;
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
// do nothing
}
@Override
public String getAbstractMethodName() {
return null
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>;
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return null;
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
// do nothing.
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return null;
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
// do nothing.
}
@Override
public String getDelegateSuperclassName() {
return null;
}
@Override
public void checkForCallingConventionDefiningCalls(Node n,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, Scope scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public String getGlobalObject() {
return "window";
}
@Override
public boolean isPropertyTestFunction(Node call) {
return false;
}
@Override
public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t,
Node callNode) {
return null;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return Collections.emptySet();
}
@Override
public Bind describeFunctionBind(Node n) {
// It would be nice to be able to identify a fn.bind call
// but that requires knowing the type of "fn".
if (n.getType() != Token.CALL) {
return null;
}
Node callTarget = n.getFirstChild();
String name = callTarget.getQualifiedName();
if (name != null) {
if (name.equals("Function.prototype.bind.call")) {
// goog.bind(fn, self, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = safeNext(fn);
Node parameters = safeNext(thisValue);
return new Bind(fn, thisValue, parameters);
}
}
if (callTarget.isGetProp()
&& callTarget.getLastChild().getString().equals("bind")
&& callTarget.getFirstChild().isFunction()) {
// (function(){}).bind(self, args...);
Node fn = callTarget.getFirstChild();
Node thisValue = callTarget.getNext();
Node parameters = safeNext(thisValue);
return new Bind(fn, thisValue, parameters);
}
return null;
}
private Node safeNext(Node n) {
if (n != null) {
return n.getNext();
}
return null;
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>() {
// By default, do nothing. Not all kinds of SourceFiles can regenerate
// code.
}
boolean hasSourceInMemory() {
return code != null;
}
/** Returns a unique name for the source file. */
@Override
public String getName() {
return fileName;
}
/** Returns whether this is an extern. */
@Override
public boolean isExtern() {
return isExternFile;
}
/** Sets that this is an extern. */
void setIsExtern(boolean newVal) {
isExternFile = newVal;
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
String js = "";
try {
// NOTE(nicksantos): Right now, this is optimized for few warnings.
// This is probably the right trade-off, but will be slow if there
// are lots of warnings in one file.
js = getCode();
} catch (IOException e) {
return null;
}
int pos = 0;
int startLine = 1;
// If we've saved a previous offset and it's for a line less than the
// one we're searching for, then start at that point.
if (lineNumber >= lastLine) {
pos = lastOffset;
startLine = lastLine;
}
for (int n = startLine; n < lineNumber; n++) {
int nextpos = js.indexOf('\n', pos);
if (nextpos == -1) {
return null;
}
pos = nextpos + 1;
}
// Remember this offset for the next search we do.
lastOffset = pos;
lastLine = lineNumber;
if (js.indexOf('\n', pos) == -1) {
// If next new line cannot be found, there are two cases
// 1. pos already reaches the end of file, then null should be returned
// 2. otherwise, return the contents between pos and the end of file.
if (pos >= js.length()) {
return null;
} else {
return js.substring(pos, js.length());
}
} else {
return js.substring(pos, js.indexOf('\n', pos));
}
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> * @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
String js = "";
try {
js = getCode();
} catch (IOException e) {
return null;
}
int pos = 0;
int startLine = Math.max(1,
lineNumber - (SOURCE_EXCERPT_REGION_LENGTH + 1) / 2 + 1);
for (int n = 1; n < startLine; n++) {
int nextpos = js.indexOf('\n', pos);
if (nextpos == -1) {
break;
}
pos = nextpos + 1;
}
int end = pos;
int endLine = startLine;
for (int n = 0; n < SOURCE_EXCERPT_REGION_LENGTH; n++, endLine++) {
end = js.indexOf('\n', end);
if (end == -1) {
break;
}
end++;
}
if (lineNumber >= endLine) {
return null;
}
if (end == -1) {
int last = js.length() - 1;
if (js.charAt(last) == '\n') {
return
new SimpleRegion(startLine, endLine, js.substring(pos, last));
} else {
return new SimpleRegion(startLine, endLine, js.substring(pos));
}
} else {
return new SimpleRegion(startLine, endLine, js.substring(pos, end));
}
}
@Override
public String toString() {
return fileName;
}
public static SourceFile fromFile(String fileName, Charset c) {
return builder().withCharset(c).buildFromFile(fileName);
}
public static SourceFile fromFile(String fileName) {
return builder().buildFromFile(fileName);
}
public static SourceFile fromFile(File file, Charset c) {
return builder().withCharset(c).buildFromFile(file);
}
public static SourceFile fromFile(File file) {
return builder().buildFromFile(file);
}
public static SourceFile fromCode(String fileName, String code) {
return builder().buildFromCode(fileName, code);
}
public static SourceFile fromCode(String fileName,
String originalPath, String code) {
return builder().withOriginalPath(originalPath)
.buildFromCode(fileName, code);
}
public static SourceFile fromInputStream(String fileName, InputStream s)
throws IOException {
return builder().buildFromInputStream(fileName, s);
}
public static SourceFile fromInputStream(String fileName,
String originalPath, InputStream s) throws IOException {
return builder().withOriginalPath(originalPath)
.buildFromInputStream(fileName, s);
}
public static SourceFile fromReader(
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for non side effecting statements such as
* <pre>
* var s = "this string is "
* "continued on the next line but you forgot the +";
* x == foo(); // should that be '='?
* foo();; // probably just a stray-semicolon. Doesn't hurt to check though
* </p>
* and generates warnings.
*
*/
final class CheckSideEffects extends AbstractPostOrderCallback {
static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning(
"JSC_USELESS_CODE",
"Suspicious code. {0}");
private final CheckLevel level;
CheckSideEffects(CheckLevel level) {
this.level = level;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.isEmpty() ||
n.isComma()) {
return;
}
if (parent == null)
return;
int pt = parent.getType();
if (pt == Token.COMMA) {
Node gramps = parent.getParent();
if (gramps.isCall() &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval.
if (n == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
n.getNext().isName()
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> &&
"eval".equals(n.getNext().getString())) {
return;
}
}
if (n == parent.getLastChild()) {
for (Node an : parent.getAncestors()) {
int ancestorType = an.getType();
if (ancestorType == Token.COMMA)
continue;
if (ancestorType != Token.EXPR_RESULT &&
ancestorType != Token.BLOCK)
return;
else
break;
}
}
} else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) {
if (pt == Token.FOR && parent.getChildCount() == 4 &&
(n == parent.getFirstChild() ||
n == parent.getFirstChild().getNext().getNext())) {
// Fall through and look for warnings for the 1st and 3rd child
// of a for.
} else {
return; // it might be ok to not have a side-effect
}
}
boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType());
if (isSimpleOp ||
!NodeUtil.mayHaveSideEffects(n, t.getCompiler())) {
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
return;
} else if (NodeUtil.isExpressionNode(n)) {
// we already reported the problem when we visited the child.
return;
}
String msg = "This code lacks side-effects. Is there a bug?";
if (n.isString()) {
msg = "Is there a missing '+' on the previous line?";
} else if (isSimpleOp) {
msg = "The result of the '" + Node.tokenToName(n.getType()) +
"' operator is not being used.";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.StaticSymbolTable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Memoize a scope creator.
*
* This allows you to make multiple passes, without worrying about
* the expense of generating Scope objects over and over again.
*
* On the other hand, you also have to be more aware of what your passes
* are doing. Scopes are memoized stupidly, so if the underlying tree
* changes, the scope may be out of sync.
*
* @author nicksantos@google.com (Nick Santos)
*/
class MemoizedScopeCreator
implements ScopeCreator, StaticSymbolTable<Var, Var> {
private final Map<Node, Scope> scopes = Maps.newHashMap();
private final ScopeCreator delegate;
/**
* @param delegate The real source of Scope objects.
*/
MemoizedScopeCreator(ScopeCreator delegate) {
this.delegate = delegate;
}
@Override
public Iterable<Var> getReferences(Var var) {
return ImmutableList.of(var);
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
@Override
public Iterable<Var> getAllSymbols() {
List<Var> vars = Lists.newArrayList();
for (Scope s : scopes.values()) {
Iterables.addAll(vars, s.getAllSymbols());
}
return vars;
}
@Override
public Scope createScope(Node n, Scope parent) {
Scope scope = scopes.get(n);
if (scope == null) {
scope
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> = delegate.createScope(n, parent);
scopes.put(n, scope);
} else {
Preconditions.checkState(parent == scope.getParent());
}
return scope;
}
Collection<Scope> getAllMemoizedScopes() {
return Collections.unmodifiableCollection(scopes.values());
}
Scope getScopeIfMemoized(Node n) {
return scopes.get(n);
}
/**
* Removes all scopes with root nodes from a given script file.
*
* @param scriptName the name of the script file to remove nodes for.
*/
void removeScopesForScript(String scriptName) {
for (Node scopeRoot : ImmutableSet.copyOf(scopes.keySet())) {
if (scriptName.equals(scopeRoot.getSourceFileName())) {
scopes.remove(scopeRoot);
}
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Prepare the AST before we do any checks or optimizations on it.
*
* This pass must run. It should bring the AST into a consistent state,
* and add annotations where necessary. It should not make any transformations
* on the tree that would lose source information, since we need that source
* information for checks.
*
* @author johnlenz@google.com (John Lenz)
*/
class PrepareAst implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean checkOnly;
PrepareAst(AbstractCompiler compiler) {
this(compiler, false);
}
PrepareAst(AbstractCompiler compiler, boolean checkOnly) {
this.compiler = compiler;
this.checkOnly = checkOnly;
}
private void reportChange() {
if (checkOnly) {
Preconditions.checkState(false, "normalizeNodeType constraints violated");
}
}
@Override
public void process(Node externs, Node root) {
if (checkOnly) {
normalizeNodeTypes(root);
} else {
// Don't perform "PrepareAnnotations" when doing checks as
// they currently aren't valid during sanity checks. In particular,
// they DIRECT_EVAL shouldn't be applied after inlining has been
// performed.
if (externs != null) {
NodeTraversal.traverse(
compiler, externs, new PrepareAnnotations(compiler));
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations(compiler));
}
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& n.getType() != Token.LABEL
&& n.getType() != Token.SWITCH) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
c.getType() != Token.BLOCK) {
Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno());
newBlock.copyInformationFrom(n);
n.replaceChild(c, newBlock);
if (c.getType() != Token.EMPTY) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isObjectLit()) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateFunctions(n, parent);
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState(objlit.isObjectLit());
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.isCall());
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.isName() &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.isFunction());
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.isAssign()) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLiteralKeyAnnotations(
Node objlit, Node key, Node value) {
Preconditions.checkState(objlit.isObjectLit());
if (key.getJSDocInfo() != null &&
value.isFunction()) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
/**
* Annotate optional and var_arg function parameters.
*/
private void annotateFunctions(Node n, Node parent) {
JSDocInfo fnInfo = NodeUtil.getFunctionJSDocInfo(n);
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr != null && typeExpr.isVarArgs()) {
arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true);
}
}
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>_VAR_DECLARATION,
HIDDEN_PROPERTY_MISMATCH,
INTERFACE_METHOD_NOT_IMPLEMENTED,
HIDDEN_INTERFACE_PROPERTY_MISMATCH);
TypeValidator(AbstractCompiler compiler) {
this.compiler = compiler;
this.typeRegistry = compiler.getTypeRegistry();
this.allValueTypes = typeRegistry.createUnionType(
STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE, NULL_TYPE, VOID_TYPE);
this.nullOrUndefined = typeRegistry.createUnionType(
NULL_TYPE, VOID_TYPE);
}
/**
* Gets a list of type violations.
*
* For each violation, one element is the expected type and the other is
* the type that is actually found. Order is not signficant.
*/
Iterable<TypeMismatch> getMismatches() {
return mismatches;
}
void setShouldReport(boolean report) {
this.shouldReport = report;
}
// All non-private methods should have the form:
// expectCondition(NodeTraversal t, Node n, ...);
// If there is a mismatch, the {@code expect} method should issue
// a warning and attempt to correct the mismatch, when possible.
/**
* Expect the type to be an object, or a type convertible to object. If the
* expectation is not met, issue a warning at the provided node's source code
* position.
* @return True if there was no warning, false if there was a mismatch.
*/
boolean expectObject(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesObjectContext()) {
mismatch(t, n, msg, type, OBJECT_TYPE);
return false;
}
return true;
}
/**
* Expect the type to be an object. Unlike expectObject, a type convertible
* to object is not acceptable.
*/
void expectActualObject(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.isObject()) {
mismatch(t, n, msg, type, OBJECT_TYPE);
}
}
/**
* Expect the type to contain an object sometimes. If the expectation is
* not met, issue a warning at the provided node's source code position.
*/
void expectAnyObject(NodeTraversal t, Node n, JSType type, String msg) {
JSType anyObjectType = getNativeType(NO_OBJECT_TYPE);
if (!anyObjectType.isSubtype(type) && !type.isEmptyType()) {
mismatch(t, n, msg, type, anyObjectType);
}
}
/**
* Expect the type to be a string, or a type convertible to string. If the
* expectation is not met, issue a warning at the provided node's source code
* position.
*/
void expectString(NodeTraversal t, Node n, JSType type, String msg) {
if (!
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>type.matchesStringContext()) {
mismatch(t, n, msg, type, STRING_TYPE);
}
}
/**
* Expect the type to be a number, or a type convertible to number. If the
* expectation is not met, issue a warning at the provided node's source code
* position.
*/
void expectNumber(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesNumberContext()) {
mismatch(t, n, msg, type, NUMBER_TYPE);
}
}
/**
* Expect the type to be a valid operand to a bitwise operator. This includes
* numbers, any type convertible to a number, or any other primitive type
* (undefined|null|boolean|string).
*/
void expectBitwiseable(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesNumberContext() && !type.isSubtype(allValueTypes)) {
mismatch(t, n, msg, type, allValueTypes);
}
}
/**
* Expect the type to be a number or string, or a type convertible to a number
* or string. If the expectation is not met, issue a warning at the provided
* node's source code position.
*/
void expectStringOrNumber(
NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesNumberContext() && !type.matchesStringContext()) {
mismatch(t, n, msg, type, NUMBER_STRING);
}
}
/**
* Expect the type to be anything but the null or void type. If the
* expectation is not met, issue a warning at the provided node's
* source code position. Note that a union type that includes the
* void type and at least one other type meets the expectation.
* @return Whether the expectation was met.
*/
boolean expectNotNullOrUndefined(
NodeTraversal t, Node n, JSType type, String msg, JSType expectedType) {
if (!type.isNoType() && !type.isUnknownType() &&
type.isSubtype(nullOrUndefined) &&
!containsForwardDeclaredUnresolvedName(type)) {
// There's one edge case right now that we don't handle well, and
// that we don't want to warn about.
// if (this.x == null) {
// this.initializeX();
// this.x.foo();
// }
// In this case, we incorrectly type x because of how we
// infer properties locally. See issue 109.
// http://code.google.com/p/closure-compiler/issues/detail?id=109
//
// We do not do this inference globally.
if (n.isGetProp() &&
!t.inGlobalScope() && type.isNullType()) {
return true;
}
mismatch
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(t, n, msg, type, expectedType);
return false;
}
return true;
}
private boolean containsForwardDeclaredUnresolvedName(JSType type) {
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
if (containsForwardDeclaredUnresolvedName(alt)) {
return true;
}
}
}
return type.isNoResolvedType();
}
/**
* Expect that the type of a switch condition matches the type of its
* case condition.
*/
void expectSwitchMatchesCase(NodeTraversal t, Node n, JSType switchType,
JSType caseType) {
// ECMA-262, page 68, step 3 of evaluation of CaseBlock,
// but allowing extra autoboxing.
// TODO(user): remove extra conditions when type annotations
// in the code base have adapted to the change in the compiler.
if (!switchType.canTestForShallowEqualityWith(caseType) &&
(caseType.autoboxesTo() == null ||
!caseType.autoboxesTo().isSubtype(switchType))) {
mismatch(t, n.getFirstChild(),
"case expression doesn't match switch",
caseType, switchType);
}
}
/**
* Expect that the first type can be addressed with GETELEM syntax,
* and that the second type is the right type for an index into the
* first type.
*
* @param t The node traversal.
* @param n The GETELEM node to issue warnings on.
* @param objType The type of the left side of the GETELEM.
* @param indexType The type inside the brackets of the GETELEM.
*/
void expectIndexMatch(NodeTraversal t, Node n, JSType objType,
JSType indexType) {
Preconditions.checkState(n.isGetElem());
Node indexNode = n.getLastChild();
if (objType.isUnknownType()) {
expectStringOrNumber(t, indexNode, indexType, "property access");
} else {
ObjectType dereferenced = objType.dereference();
if (dereferenced != null && dereferenced.getIndexType() != null) {
expectCanAssignTo(t, indexNode, indexType, dereferenced.getIndexType(),
"restricted index type");
} else if (dereferenced != null && dereferenced.isArrayType()) {
expectNumber(t, indexNode, indexType, "array access");
} else if (objType.matchesObjectContext()) {
expectString(t, indexNode, indexType, "property access");
} else {
mismatch(t, n, "only arrays or objects can be accessed",
objType,
typeRegistry.createUnionType(ARRAY_TYPE, OBJECT_TYPE));
}
}
}
/**
* Expect that the first
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> type can be assigned to a symbol of the second
* type.
*
* @param t The node traversal.
* @param n The node to issue warnings on.
* @param rightType The type on the RHS of the assign.
* @param leftType The type of the symbol on the LHS of the assign.
* @param owner The owner of the property being assigned to.
* @param propName The name of the property being assigned to.
* @return True if the types matched, false otherwise.
*/
boolean expectCanAssignToPropertyOf(NodeTraversal t, Node n, JSType rightType,
JSType leftType, Node owner, String propName) {
// The NoType check is a hack to make typedefs work ok.
if (!leftType.isNoType() && !rightType.canAssignTo(leftType)) {
if (bothIntrinsics(rightType, leftType)) {
// We have a superior warning for this mistake, which gives you
// the line numbers of both types.
registerMismatch(rightType, leftType, null);
} else {
mismatch(t, n,
"assignment to property " + propName + " of " +
getReadableJSTypeName(owner, true),
rightType, leftType);
}
return false;
}
return true;
}
/**
* Expect that the first type can be assigned to a symbol of the second
* type.
*
* @param t The node traversal.
* @param n The node to issue warnings on.
* @param rightType The type on the RHS of the assign.
* @param leftType The type of the symbol on the LHS of the assign.
* @param msg An extra message for the mismatch warning, if necessary.
* @return True if the types matched, false otherwise.
*/
boolean expectCanAssignTo(NodeTraversal t, Node n, JSType rightType,
JSType leftType, String msg) {
if (!rightType.canAssignTo(leftType)) {
if (bothIntrinsics(rightType, leftType)) {
// We have a superior warning for this mistake, which gives you
// the line numbers of both types.
registerMismatch(rightType, leftType, null);
} else {
mismatch(t, n, msg, rightType, leftType);
}
return false;
}
return true;
}
private boolean bothIntrinsics(JSType rightType, JSType leftType) {
return (leftType.isConstructor() || leftType.isEnumType()) &&
(rightType.isConstructor() || rightType.isEnumType());
}
/**
* Expect that the type of an argument matches the type of the parameter
* that it's fulfilling.
*
* @param t The node traversal.
* @param n The node to issue warnings on.
* @param argType The type of the argument
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.
* @param paramType The type of the parameter.
* @param callNode The call node, to help with the warning message.
* @param ordinal The argument ordinal, to help with the warning message.
*/
void expectArgumentMatchesParameter(NodeTraversal t, Node n, JSType argType,
JSType paramType, Node callNode, int ordinal) {
if (!argType.canAssignTo(paramType)) {
mismatch(t, n,
String.format("actual parameter %d of %s does not match " +
"formal parameter", ordinal,
getReadableJSTypeName(callNode.getFirstChild(), false)),
argType, paramType);
}
}
/**
* Expect that the first type can override a property of the second
* type.
*
* @param t The node traversal.
* @param n The node to issue warnings on.
* @param overridingType The overriding type.
* @param hiddenType The type of the property being overridden.
* @param propertyName The name of the property, for use in the
* warning message.
* @param ownerType The type of the owner of the property, for use
* in the warning message.
*/
void expectCanOverride(NodeTraversal t, Node n, JSType overridingType,
JSType hiddenType, String propertyName, JSType ownerType) {
if (!overridingType.canAssignTo(hiddenType)) {
registerMismatch(overridingType, hiddenType,
report(t.makeError(n, HIDDEN_PROPERTY_MISMATCH, propertyName,
ownerType.toString(), hiddenType.toString(),
overridingType.toString())));
}
}
/**
* Expect that the first type is the direct superclass of the second type.
*
* @param t The node traversal.
* @param n The node where warnings should point to.
* @param superObject The expected super instance type.
* @param subObject The sub instance type.
*/
void expectSuperType(NodeTraversal t, Node n, ObjectType superObject,
ObjectType subObject) {
FunctionType subCtor = subObject.getConstructor();
ObjectType declaredSuper =
subObject.getImplicitPrototype().getImplicitPrototype();
if (!declaredSuper.equals(superObject)) {
if (declaredSuper.equals(getNativeType(OBJECT_TYPE))) {
registerMismatch(superObject, declaredSuper, report(
t.makeError(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString())));
} else {
mismatch(t.getSourceName(), n,
"mismatch in declaration of superclass type",
superObject, declaredSuper);
}
// Correct the super type.
if (!subCtor.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* should
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> be either a subtype or supertype of the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) {
castType = castType.restrictByNotNullOrUndefined();
type = type.restrictByNotNullOrUndefined();
if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) {
registerMismatch(type, castType, report(t.makeError(n, INVALID_CAST,
castType.toString(), type.toString())));
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
*/
void expectUndeclaredVariable(String sourceName, CompilerInput input,
Node n, Node parent, Var var, String variableName, JSType newType) {
boolean allowDupe = false;
if (n.isGetProp() ||
NodeUtil.isObjectLitKey(n, parent)) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type. We should redeclare it at the new input site.
if (var.input == null) {
Scope s = var.getScope();
s.undeclare(var);
s.declare(variableName, n, varType, input, false);
n.setJSType(varType);
if (parent.isVar()) {
if (n.getFirstChild() != null) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.isFunction());
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().isExprResult()) ||
!newType.equals(varType)) {
report(JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented and correctly typed.
*/
void expectAllInterfaceProperties(NodeTraversal t, Node n,
FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
expectInterfaceProperty(t, n, instance, implemented, prop);
}
}
}
}
/**
* Expect that the peroperty in an interface that this type implements is
* implemented and correctly typed.
*/
private void expectInterfaceProperty(NodeTraversal t, Node n,
ObjectType instance, ObjectType implementedInterface, String prop) {
if (!instance.hasProperty(prop)) {
// Not implemented
String sourceName = n.getSourceFileName();
sourceName = sourceName == null ? "" : sourceName;
registerMismatch(instance, implementedInterface,
report(JSError.make(sourceName, n,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implementedInterface.toString(), instance.toString())));
} else {
JSType found = instance.getPropertyType(prop);
JSType required
= implementedInterface.getImplicitPrototype().getPropertyType(prop);
found = found.restrictByNotNullOrUndefined();
required = required.restrictByNotNullOrUndefined();
if (!found.canAssignTo(required)) {
// Implemented, but not correctly typed
FunctionType constructor
= implementedInterface.toObjectType().getConstructor();
registerMismatch(found, required, report(t.makeError(n,
HIDDEN_INTERFACE_PROPERTY_MISMATCH, prop,
constructor.getTopMostDefiningType(prop).toString(),
required.toString(), found.toString())));
}
}
}
/**
* Report a type mismatch
*/
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSType required) {
mismatch(t.getSourceName(), n, msg, found, required);
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
}
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSTypeNative required) {
mismatch(t, n, msg, found, getNativeType(required));
}
private void mismatch(String sourceName, Node n,
String msg, JSType found, JSType required) {
registerMismatch(found, required, report(
JSError.make(sourceName, n, TYPE_MISMATCH_WARNING,
formatFoundRequired(msg, found, required))));
}
private void registerMismatch(JSType found, JSType required, JSError error) {
// Don't register a mismatch for differences in null or undefined or if the
// code didn't downcast.
found = found.restrictByNotNullOrUndefined();
required = required.restrictByNotNullOrUndefined();
if (found.canAssignTo(required) || required.canAssignTo(found)) {
return;
}
mismatches.add(new TypeMismatch(found, required, error));
if (found.isFunctionType() &&
required.isFunctionType()) {
FunctionType fnTypeA = found.toMaybeFunctionType();
FunctionType fnTypeB = required.toMaybeFunctionType();
Iterator<Node> paramItA = fnTypeA.getParameters().iterator();
Iterator<Node> paramItB = fnTypeB.getParameters().iterator();
while (paramItA.hasNext() && paramItB.hasNext()) {
registerIfMismatch(paramItA.next().getJSType(),
paramItB.next().getJSType(), error);
}
registerIfMismatch(
fnTypeA.getReturnType(), fnTypeB.getReturnType(), error);
}
}
private void registerIfMismatch(
JSType found, JSType required, JSError error) {
if (found != null && required != null &&
!found.canAssignTo(required)) {
registerMismatch(found, required, error);
}
}
/**
* Formats a found/required error message.
*/
private String formatFoundRequired(String description, JSType found,
JSType required) {
return MessageFormat.format(FOUND_REQUIRED, description, found, required);
}
/**
* Given a node, get a human-readable name for the type of that node so
* that will be easy for the programmer to find the original declaration.
*
* For example, if SubFoo's property "bar" might have the human-readable
* name "Foo.prototype.bar".
*
* @param n The node.
* @param dereference If true, the type of the node will be dereferenced
* to an Object type, if possible.
*/
String getReadableJSTypeName(Node n, boolean dereference) {
// If we're analyzing a GETPROP, the property may be inherited by the
// prototype chain. So climb the prototype chain and find out where
// the property was
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> originally defined.
if (n.isGetProp()) {
ObjectType objectType = getJSType(n.getFirstChild()).dereference();
if (objectType != null) {
String propName = n.getLastChild().getString();
if (objectType.getConstructor() != null &&
objectType.getConstructor().isInterface()) {
objectType = FunctionType.getTopDefiningInterface(
objectType, propName);
} else {
// classes
while (objectType != null && !objectType.hasOwnProperty(propName)) {
objectType = objectType.getImplicitPrototype();
}
}
// Don't show complex function names or anonymous types.
// Instead, try to get a human-readable type name.
if (objectType != null &&
(objectType.getConstructor() != null ||
objectType.isFunctionPrototypeType())) {
return objectType.toString() + "." + propName;
}
}
}
JSType type = getJSType(n);
if (dereference) {
ObjectType dereferenced = type.dereference();
if (dereferenced != null) {
type = dereferenced;
}
}
String qualifiedName = n.getQualifiedName();
if (type.isFunctionPrototypeType() ||
(type.toObjectType() != null &&
type.toObjectType().getConstructor() != null)) {
return type.toString();
} else if (qualifiedName != null) {
return qualifiedName;
} else if (type.isFunctionType()) {
// Don't show complex function names.
return "function";
} else {
return type.toString();
}
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(user): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
private JSType getNativeType(JSTypeNative typeId) {
return typeRegistry.getNativeType(typeId);
}
private JSError report(JSError error) {
if (shouldReport) {
compiler.report(error);
}
return error;
}
/**
* Signals that the first type and the second type have been
* used interchangeably.
*
* Type-based optimizations should take this into account
* so that they don't wreck code with type warnings.
*/
static class TypeMismatch {
final JSType typeA;
final JSType typeB;
final JSError src;
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
// Don't traverse functions that are constructors or have the @this
// or @override annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
}
if (parent != null && parent.isAssign()) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.isGetProp() &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.isGetProp() &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isThis() && shouldReportThis(n, parent)) {
compiler.report
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(t.makeError(n, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.isVar()) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>removeAll(allDefines.keySet());
unusedReplacements.removeAll(KNOWN_DEFINES);
for (String unknownDefine : unusedReplacements) {
compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine));
}
}
private static String format(MessageFormat format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.isSubtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
Ref decl = name.getDeclaration();
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
decl.getSourceName(),
decl.node, INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else {
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.isVar() && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
// a define is allowed. Otherwise, it's not allowed.
private final Deque<Integer> assignAllowed;
CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) {
this.compiler = compiler;
this.allDefines = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
Ref decl = name.getDeclaration();
if (decl != null) {
allRefInfo.put(decl.node,
new RefInfo(decl, name));
}
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
// If there's a TWIN def, only put one of the twins in.
if (ref.getTwin() == null || !ref.getTwin().isSet()) {
allRefInfo.put(ref.node, new RefInfo(ref, name));
}
}
}
}
/**
* Get a map of {@link DefineInfo} structures, keyed by the name of
* the define.
*/
Map<String, DefineInfo> getAllDefines() {
return allDefines;
}
/**
* Keeps track of whether the traversal is in a conditional branch.
* We traverse all nodes of the parse tree.
*/
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
updateAssignAllowedStack(n, true);
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
RefInfo refInfo = allRefInfo.get(n);
if (refInfo != null) {
Ref ref = refInfo.ref;
Name name = refInfo.name;
String fullName = name.getFullName();
switch (ref.type) {
case SET_FROM_GLOBAL:
case SET_FROM_LOCAL:
Node valParent = getValueParent(ref);
Node val = valParent.getLastChild();
if (valParent.isAssign() && name.isSimpleName() &&
name.getDeclaration() == ref) {
// For defines, it's an error if a simple name is assigned
// before it's declared
compiler.report(
t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName));
} else if (process
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>DefineAssignment(t, fullName, val, valParent)) {
// remove the assignment so that the variable is still declared,
// but no longer assigned to a value, e.g.,
// DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.isAssign()) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.isName());
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.isCall()) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, expecially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> */
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
} else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
// The define was already initialized, but this is a safe
// re-assignment.
return true;
} else {
// The define was already initialized, and this is an unsafe
// re-assignment.
compiler.report(
t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR,
name, info.getReasonWhyNotAssignable()));
}
}
return false;
}
/**
* Gets the parent node of the value for any assignment to a Name.
* For example, in the assignment
* {@code var x = 3;}
* the parent would be the NAME node.
*/
private static Node getValueParent(Ref ref) {
// there are two types of declarations: VARs and ASSIGNs
return ref.node.getParent() != null &&
ref.node.getParent().isVar() ?
ref.node : ref.node.getParent();
}
/**
* Records the fact that because of the current node in the node traversal,
* the define can't ever be assigned again.
*
* @param info
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.google.javascript.rhino.jstype.StaticSlot;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Type inference within a script node or a function body, using the data-flow
* analysis framework.
*
*/
class TypeInference
extends DataFlowAnalysis.BranchedForwardDataFlowAnalysis<Node, FlowScope> {
static final DiagnosticType TEMPLATE_TYPE_NOT_OBJECT_TYPE =
DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_NOT_OBJECT_TYPE",
"The template type must be an object type.\nActual: {0}");
static final DiagnosticType TEMPLATE_TYPE_OF_THIS_EXPECTED =
DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_OF_THIS_EXPECTED",
"A function type with the template type as the type of this must be a " +
"parameter type");
static final DiagnosticType FUNCTION_LITERAL_UNDEFINED_THIS =
DiagnosticType.warning(
"JSC_FUNCTION_LITERAL_UNDEFINED_THIS",
"Function literal argument refers to undefined this argument");
private final AbstractCompiler compiler;
private final JSTypeRegistry registry;
private final ReverseAbstractInterpreter reverseInterpreter;
private final Scope syntacticScope;
private final FlowScope functionScope;
private final FlowScope bottomScope;
private final Map<String, AssertionFunctionSpec> assertionFunctionsMap;
TypeInference(AbstractCompiler compiler, ControlFlowGraph<Node> cfg,
ReverseAbstractInterpreter reverseInterpreter,
Scope functionScope,
Map<String, AssertionFunctionSpec> assertionFunctionsMap) {
super(cfg, new LinkedFlowScope.FlowScopeJoinOp());
this.compiler = compiler;
this.registry = compiler.getTypeRegistry();
this.reverseInterpreter = reverseInterpreter;
this.syntacticScope = functionScope;
this.functionScope = LinkedFlowScope.createEntryLattice(functionScope);
this.assertionFunctionsMap = assertionFunctionsMap;
// For each local variable declared with the VAR keyword, the entry
// type is VOID.
Iterator<Var> varIt =
functionScope.getDeclarativelyUnboundVarsWithoutTypes();
while (varIt.hasNext()) {
Var var = varIt.next();
if (isUnflowable(var)) {
continue;
}
this.functionScope.inferSlotType(
var.getName(), getNativeType(VOID_TYPE));
}
this.bottomScope = LinkedFlowScope.createEntryLattice(
new Scope(functionScope.getRootNode(), functionScope.getTypeOfThis()));
}
@Override
FlowScope createInitialEstimateLattice() {
return bottomScope;
}
@Override
FlowScope createEntryLattice() {
return functionScope;
}
@Override
FlowScope flowThrough(Node n, FlowScope input) {
// If we have not walked a path from <entry> to <n>,
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> then we don't
// want to infer anything about this scope.
if (input == bottomScope) {
return input;
}
FlowScope output = input.createChildFlowScope();
output = traverse(n, output);
return output;
}
@Override
@SuppressWarnings("fallthrough")
List<FlowScope> branchedFlowThrough(Node source, FlowScope input) {
// NOTE(nicksantos): Right now, we just treat ON_EX edges like UNCOND
// edges. If we wanted to be perfect, we'd actually JOIN all the out
// lattices of this flow with the in lattice, and then make that the out
// lattice for the ON_EX edge. But it's probably to expensive to be
// worthwhile.
FlowScope output = flowThrough(source, input);
Node condition = null;
FlowScope conditionFlowScope = null;
BooleanOutcomePair conditionOutcomes = null;
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(source);
List<FlowScope> result = Lists.newArrayListWithCapacity(branchEdges.size());
for (DiGraphEdge<Node, Branch> branchEdge : branchEdges) {
Branch branch = branchEdge.getValue();
FlowScope newScope = output;
switch (branch) {
case ON_TRUE:
if (NodeUtil.isForIn(source)) {
// item is assigned a property name, so its type should be string.
Node item = source.getFirstChild();
Node obj = item.getNext();
FlowScope informed = traverse(obj, output.createChildFlowScope());
if (item.isVar()) {
item = item.getFirstChild();
}
if (item.isName()) {
JSType iterKeyType = getNativeType(STRING_TYPE);
ObjectType objType = getJSType(obj).dereference();
JSType objIndexType = objType == null ?
null : objType.getIndexType();
if (objIndexType != null && !objIndexType.isUnknownType()) {
JSType narrowedKeyType =
iterKeyType.getGreatestSubtype(objIndexType);
if (!narrowedKeyType.isEmptyType()) {
iterKeyType = narrowedKeyType;
}
}
redeclareSimpleVar(informed, item, iterKeyType);
}
newScope = informed;
break;
}
// FALL THROUGH
case ON_FALSE:
if (condition == null) {
condition = NodeUtil.getConditionExpression(source);
if (condition == null && source.isCase()) {
condition = source;
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope = traverse(
condition.getFirstChild(), output.createChildFlowScope());
}
}
}
if (condition != null) {
if (condition.isAnd()
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> ||
condition.isOr()) {
// When handling the short-circuiting binary operators,
// the outcome scope on true can be different than the outcome
// scope on false.
//
// TODO(nicksantos): The "right" way to do this is to
// carry the known outcome all the way through the
// recursive traversal, so that we can construct a
// different flow scope based on the outcome. However,
// this would require a bunch of code and a bunch of
// extra computation for an edge case. This seems to be
// a "good enough" approximation.
// conditionOutcomes is cached from previous iterations
// of the loop.
if (conditionOutcomes == null) {
conditionOutcomes = condition.isAnd() ?
traverseAnd(condition, output.createChildFlowScope()) :
traverseOr(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition,
conditionOutcomes.getOutcomeFlowScope(
condition.getType(), branch == Branch.ON_TRUE),
branch == Branch.ON_TRUE);
} else {
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope =
traverse(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition, conditionFlowScope, branch == Branch.ON_TRUE);
}
}
break;
}
result.add(newScope.optimize());
}
return result;
}
private FlowScope traverse(Node n, FlowScope scope) {
switch (n.getType()) {
case Token.ASSIGN:
scope = traverseAssign(n, scope);
break;
case Token.NAME:
scope = traverseName(n, scope);
break;
case Token.GETPROP:
scope = traverseGetProp(n, scope);
break;
case Token.AND:
scope = traverseAnd(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.OR:
scope = traverseOr(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.HOOK:
scope = traverseHook(n, scope);
break;
case Token.OBJECTLIT:
scope = traverseObjectLiteral(n, scope);
break;
case Token.CALL:
scope = traverseCall(n, scope);
break;
case Token.NEW:
scope = traverseNew(n, scope);
break;
case Token.ASSIGN_ADD:
case Token.ADD:
scope = traverseAdd(n, scope);
break;
case Token.POS:
case Token.NEG:
scope = traverse(n.getFirstChild(), scope); // Find types.
n.setJSType(get
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>NativeType(NUMBER_TYPE));
break;
case Token.ARRAYLIT:
scope = traverseArrayLiteral(n, scope);
break;
case Token.THIS:
n.setJSType(scope.getTypeOfThis());
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
case Token.MUL:
case Token.SUB:
case Token.DEC:
case Token.INC:
case Token.BITNOT:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.PARAM_LIST:
scope = traverse(n.getFirstChild(), scope);
n.setJSType(getJSType(n.getFirstChild()));
break;
case Token.COMMA:
scope = traverseChildren(n, scope);
n.setJSType(getJSType(n.getLastChild()));
break;
case Token.TYPEOF:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.DELPROP:
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
case Token.NOT:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.INSTANCEOF:
case Token.IN:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.GETELEM:
scope = traverseGetElem(n, scope);
break;
case Token.EXPR_RESULT:
scope = traverseChildren(n, scope);
if (n.getFirstChild().isGetProp()) {
ensurePropertyDeclared(n.getFirstChild());
}
break;
case Token.SWITCH:
scope = traverse(n.getFirstChild(), scope);
break;
case Token.VAR:
case Token.RETURN:
case Token.THROW:
scope = traverseChildren(n, scope);
break;
case Token.CATCH:
scope = traverseCatch(n, scope);
break;
}
if (n.getType() != Token.FUNCTION) {
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>hasType()) {
JSType castType = info.getType().evaluate(syntacticScope, registry);
// A stubbed type cast on a qualified name should take
// effect for all subsequent accesses of that name,
// so treat it the same as an assign to that name.
if (n.isQualifiedName() &&
n.getParent().isExprResult()) {
updateScopeForTypeChange(scope, n, n.getJSType(), castType);
}
n.setJSType(castType);
}
}
return scope;
}
/**
* Any value can be thrown, so it's really impossible to determine the type
* of a CATCH param. Treat it as the UNKNOWN type.
*/
private FlowScope traverseCatch(Node n, FlowScope scope) {
Node name = n.getFirstChild();
JSType type = getNativeType(JSTypeNative.UNKNOWN_TYPE);
name.setJSType(type);
redeclareSimpleVar(scope, name, type);
return scope;
}
private FlowScope traverseAssign(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = getJSType(right);
n.setJSType(rightType);
updateScopeForTypeChange(scope, left, leftType, rightType);
return scope;
}
/**
* Updates the scope according to the result of a type change, like
* an assignment or a type cast.
*/
private void updateScopeForTypeChange(
FlowScope scope, Node left, JSType leftType, JSType resultType) {
Preconditions.checkNotNull(resultType);
switch (left.getType()) {
case Token.NAME:
String varName = left.getString();
Var var = syntacticScope.getVar(varName);
// When looking at VAR initializers for declared VARs, we trust
// the declared type over the type it's being initialized to.
// This has two purposes:
// 1) We avoid re-declaring declared variables so that built-in
// types defined in externs are not redeclared.
// 2) When there's a lexical closure like
// /** @type {?string} */ var x = null;
// function f() { x = 'xyz'; }
// the inference will ignore the lexical closure,
// which is just wrong. This bug needs to be fixed eventually.
boolean isVarDeclaration = left.hasChildren();
if (!isVarDeclaration || var == null || var.isTypeInferred()) {
redeclareSimpleVar(scope, left, resultType);
}
left.setJSType(isVarDeclaration || leftType == null ?
resultType : null);
if (var != null && var.isTypeInferred()) {
JSType
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>ScopeCreator}.
*/
private void ensurePropertyDeclared(Node getprop) {
ObjectType ownerType = ObjectType.cast(
getJSType(getprop.getFirstChild()).restrictByNotNullOrUndefined());
if (ownerType != null) {
ensurePropertyDeclaredHelper(getprop, ownerType);
}
}
/**
* Declares a property on its owner, if necessary.
* @return True if a property was declared.
*/
private boolean ensurePropertyDeclaredHelper(
Node getprop, ObjectType objectType) {
String propName = getprop.getLastChild().getString();
String qName = getprop.getQualifiedName();
if (qName != null) {
Var var = syntacticScope.getVar(qName);
if (var != null && !var.isTypeInferred()) {
// Handle normal declarations that could not be addressed earlier.
if (propName.equals("prototype") ||
// Handle prototype declarations that could not be addressed earlier.
(!objectType.hasOwnProperty(propName) &&
(!objectType.isInstanceType() ||
(var.isExtern() && !objectType.isNativeObjectType())))) {
return objectType.defineDeclaredProperty(
propName, var.getType(), getprop);
}
}
}
return false;
}
private FlowScope traverseName(Node n, FlowScope scope) {
String varName = n.getString();
Node value = n.getFirstChild();
JSType type = n.getJSType();
if (value != null) {
scope = traverse(value, scope);
updateScopeForTypeChange(scope, n, n.getJSType() /* could be null */,
getJSType(value));
return scope;
} else {
StaticSlot<JSType> var = scope.getSlot(varName);
if (var != null) {
// There are two situations where we don't want to use type information
// from the scope, even if we have it.
// 1) The var is escaped in a weird way, e.g.,
// function f() { var x = 3; function g() { x = null } (x); }
boolean isInferred = var.isTypeInferred();
boolean unflowable = isInferred &&
isUnflowable(syntacticScope.getVar(varName));
// 2) We're reading type information from another scope for an
// inferred variable.
// var t = null; function f() { (t); }
boolean nonLocalInferredSlot =
isInferred &&
syntacticScope.getParent() != null &&
var == syntacticScope.getParent().getSlot(varName);
if (!unflowable && !nonLocalInferredSlot) {
type = var.getType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
}
}
}
}
n.setJSType(type);
return scope;
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
}
/** Traverse each element of the array. */
private FlowScope traverseArrayLiteral(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(ARRAY_TYPE));
return scope;
}
private FlowScope traverseObjectLiteral(Node n, FlowScope scope) {
JSType type = n.getJSType();
Preconditions.checkNotNull(type);
for (Node name = n.getFirstChild(); name != null; name = name.getNext()) {
scope = traverse(name.getFirstChild(), scope);
}
// Object literals can be reflected on other types, or changed with
// type casts.
// See CodingConvention#getObjectLiteralCase and goog.object.reflect.
// Ignore these types of literals.
// TODO(nicksantos): There should be an "anonymous object" type that
// we can check for here.
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
return scope;
}
boolean hasLendsName = n.getJSDocInfo() != null &&
n.getJSDocInfo().getLendsName() != null;
if (objectType.hasReferenceName() && !hasLendsName) {
return scope;
}
for (Node name = n.getFirstChild(); name != null;
name = name.getNext()) {
Node value = name.getFirstChild();
String memberName = NodeUtil.getObjectLitKeyName(name);
if (memberName != null) {
JSType rawValueType = name.getFirstChild().getJSType();
JSType valueType = NodeUtil.getObjectLitKeyTypeFromValueType(
name, rawValueType);
if (valueType == null) {
valueType = getNativeType(UNKNOWN_TYPE);
}
objectType.defineInferredProperty(memberName, valueType, name);
} else {
n.setJSType(getNativeType(UNKNOWN_TYPE));
}
}
return scope;
}
private FlowScope traverseAdd(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = left.getNext();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = right.getJSType();
JSType type = getNativeType(UNKNOWN_TYPE);
if (leftType != null && rightType != null) {
boolean leftIsUnknown = leftType.isUnknownType();
boolean rightIsUnknown = rightType.isUnknownType();
if (leftIsUnknown && rightIsUnknown) {
type = getNativeType(UNKNOWN_TYPE);
} else if ((!leftIsUnknown && leftType.isString()) ||
(!rightIsUnknown && rightType.isString())) {
type = getNativeType(STRING_TYPE);
} else if (leftIsUnknown || rightIsUnknown) {
type
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> = getNativeType(UNKNOWN_TYPE);
} else if (isAddedAsNumber(leftType) && isAddedAsNumber(rightType)) {
type = getNativeType(NUMBER_TYPE);
} else {
type = registry.createUnionType(STRING_TYPE, NUMBER_TYPE);
}
}
n.setJSType(type);
if (n.getType() == Token.ASSIGN_ADD) {
updateScopeForTypeChange(scope, left, leftType, type);
}
return scope;
}
private boolean isAddedAsNumber(JSType type) {
return type.isSubtype(registry.createUnionType(VOID_TYPE, NULL_TYPE,
NUMBER_VALUE_OR_OBJECT_TYPE, BOOLEAN_TYPE, BOOLEAN_OBJECT_TYPE));
}
private FlowScope traverseHook(Node n, FlowScope scope) {
Node condition = n.getFirstChild();
Node trueNode = condition.getNext();
Node falseNode = n.getLastChild();
// verify the condition
scope = traverse(condition, scope);
// reverse abstract interpret the condition to produce two new scopes
FlowScope trueScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, true);
FlowScope falseScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, false);
// traverse the true node with the trueScope
traverse(trueNode, trueScope.createChildFlowScope());
// traverse the false node with the falseScope
traverse(falseNode, falseScope.createChildFlowScope());
// meet true and false nodes' types and assign
JSType trueType = trueNode.getJSType();
JSType falseType = falseNode.getJSType();
if (trueType != null && falseType != null) {
n.setJSType(trueType.getLeastSupertype(falseType));
} else {
n.setJSType(null);
}
return scope.createChildFlowScope();
}
private FlowScope traverseCall(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node left = n.getFirstChild();
JSType functionType = getJSType(left).restrictByNotNullOrUndefined();
if (functionType != null) {
if (functionType.isFunctionType()) {
FunctionType fnType = functionType.toMaybeFunctionType();
n.setJSType(fnType.getReturnType());
updateTypeOfParameters(n, fnType);
updateTypeOfThisOnClosure(n, fnType);
} else if (functionType.equals(getNativeType(CHECKED_UNKNOWN_TYPE))) {
n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE));
}
}
scope = tightenTypesAfterAssertions(scope, n);
return scope;
}
private FlowScope tightenTypesAfterAssertions(FlowScope scope,
Node callNode) {
Node
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> left = callNode.getFirstChild();
Node firstParam = left.getNext();
AssertionFunctionSpec assertionFunctionSpec =
assertionFunctionsMap.get(left.getQualifiedName());
if (assertionFunctionSpec == null || firstParam == null) {
return scope;
}
Node assertedNode = assertionFunctionSpec.getAssertedParam(firstParam);
if (assertedNode == null) {
return scope;
}
JSTypeNative assertedType = assertionFunctionSpec.getAssertedType();
String assertedNodeName = assertedNode.getQualifiedName();
// Handle assertions that enforce expressions evaluate to true.
if (assertedType == null) {
if (assertedNodeName != null) {
JSType type = getJSType(assertedNode);
JSType narrowed = type.restrictByNotNullOrUndefined();
if (type != narrowed) {
scope = narrowScope(scope, assertedNode, narrowed);
callNode.setJSType(narrowed);
}
} else if (assertedNode.isAnd() ||
assertedNode.isOr()) {
BooleanOutcomePair conditionOutcomes =
traverseWithinShortCircuitingBinOp(assertedNode, scope);
scope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
assertedNode, conditionOutcomes.getOutcomeFlowScope(
assertedNode.getType(), true), true);
}
} else if (assertedNodeName != null) {
// Handle assertions that enforce expressions are of a certain type.
JSType type = getJSType(assertedNode);
JSType narrowed = type.getGreatestSubtype(getNativeType(assertedType));
if (type != narrowed) {
scope = narrowScope(scope, assertedNode, narrowed);
callNode.setJSType(narrowed);
}
}
return scope;
}
private FlowScope narrowScope(FlowScope scope, Node node, JSType narrowed) {
scope = scope.createChildFlowScope();
if (node.isGetProp()) {
scope.inferQualifiedSlot(
node, node.getQualifiedName(), getJSType(node), narrowed);
} else {
redeclareSimpleVar(scope, node, narrowed);
}
return scope;
}
/**
* For functions with function parameters, type inference will set the type of
* a function literal argument from the function parameter type.
*/
private void updateTypeOfParameters(Node n, FunctionType fnType) {
int i = 0;
int childCount = n.getChildCount();
for (Node iParameter : fnType.getParameters()) {
if (i + 1 >= childCount) {
// TypeCheck#visitParametersList will warn so we bail.
return;
}
JSType iParameterType = getJSType(iParameter);
Node iArgument = n.getChildAtIndex(i + 1);
JSType iArgumentType = get
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>JSType(iArgument);
inferPropertyTypesToMatchConstraint(iArgumentType, iParameterType);
if (iParameterType.isFunctionType()) {
FunctionType iParameterFnType = iParameterType.toMaybeFunctionType();
if (iArgument.isFunction() &&
iArgumentType.isFunctionType() &&
iArgument.getJSDocInfo() == null) {
iArgument.setJSType(iParameterFnType);
}
}
i++;
}
}
/**
* For functions with function(this: T, ...) and T as parameters, type
* inference will set the type of this on a function literal argument to the
* the actual type of T.
*/
private void updateTypeOfThisOnClosure(Node n, FunctionType fnType) {
// TODO(user): Make the template logic more general.
if (fnType.getTemplateTypeName() == null) {
return;
}
int i = 0;
int childCount = n.getChildCount();
// Find the parameter whose type is the template type.
for (Node iParameter : fnType.getParameters()) {
JSType iParameterType =
getJSType(iParameter).restrictByNotNullOrUndefined();
if (iParameterType.isTemplateType()) {
// Find the actual type of this argument.
ObjectType iArgumentType = null;
if (i + 1 < childCount) {
Node iArgument = n.getChildAtIndex(i + 1);
iArgumentType = getJSType(iArgument)
.restrictByNotNullOrUndefined()
.collapseUnion()
.toObjectType();
if (iArgumentType == null) {
compiler.report(
JSError.make(NodeUtil.getSourceName(iArgument), iArgument,
TEMPLATE_TYPE_NOT_OBJECT_TYPE,
getJSType(iArgument).toString()));
return;
}
}
// Find the parameter whose type is function(this: T, ...)
boolean foundTemplateTypeOfThisParameter = false;
int j = 0;
for (Node jParameter : fnType.getParameters()) {
JSType jParameterType =
getJSType(jParameter).restrictByNotNullOrUndefined();
if (jParameterType.isFunctionType()) {
FunctionType jParameterFnType = jParameterType.toMaybeFunctionType();
if (jParameterFnType.getTypeOfThis().equals(iParameterType)) {
foundTemplateTypeOfThisParameter = true;
// Find the actual type of the this argument.
if (j + 1 >= childCount) {
// TypeCheck#visitParameterList will warn so we bail.
return;
}
Node jArgument = n.getChildAtIndex(j + 1);
JSType jArgumentType = getJSType(jArgument);
if (jArgument.isFunction() &&
jArgumentType.isFunctionType()) {
if (iArgumentType != null &&
// null and undefined get filtered out above.
!iArgumentType.isNoType())
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> {
// If it's an function expression, update the type of this
// using the actual type of T.
FunctionType jArgumentFnType = jArgumentType.toMaybeFunctionType();
if (jArgumentFnType.getTypeOfThis().isUnknownType()) {
// The new type will be picked up when we traverse the inner
// function.
jArgument.setJSType(
registry.createFunctionTypeWithNewThisType(
jArgumentFnType, iArgumentType));
}
} else {
// Warn if the anonymous function literal references this.
if (NodeUtil.referencesThis(
NodeUtil.getFunctionBody(jArgument))) {
compiler.report(JSError.make(NodeUtil.getSourceName(n), n,
FUNCTION_LITERAL_UNDEFINED_THIS));
}
}
}
// TODO(user): Add code to TypeCheck to check that the
// types of the arguments match.
}
}
j++;
}
if (!foundTemplateTypeOfThisParameter) {
compiler.report(JSError.make(NodeUtil.getSourceName(n), n,
TEMPLATE_TYPE_OF_THIS_EXPECTED));
return;
}
}
i++;
}
}
private FlowScope traverseNew(Node n, FlowScope scope) {
Node constructor = n.getFirstChild();
scope = traverse(constructor, scope);
JSType constructorType = constructor.getJSType();
JSType type = null;
if (constructorType != null) {
constructorType = constructorType.restrictByNotNullOrUndefined();
if (constructorType.isUnknownType()) {
type = getNativeType(UNKNOWN_TYPE);
} else {
FunctionType ct = constructorType.toMaybeFunctionType();
if (ct == null && constructorType instanceof FunctionType) {
// If constructorType is a NoObjectType, then toMaybeFunctionType will
// return null. But NoObjectType implements the FunctionType
// interface, precisely because it can validly construct objects.
ct = (FunctionType) constructorType;
}
if (ct != null && ct.isConstructor()) {
type = ct.getInstanceType();
}
}
}
n.setJSType(type);
for (Node arg = constructor.getNext(); arg != null; arg = arg.getNext()) {
scope = traverse(arg, scope);
}
return scope;
}
private BooleanOutcomePair traverseAnd(Node n, FlowScope scope) {
return traverseShortCircuitingBinOp(n, scope, true);
}
private FlowScope traverseChildren(Node n, FlowScope scope) {
for (Node el = n.getFirstChild(); el != null; el = el.getNext()) {
scope = traverse(el, scope);
}
return scope;
}
private FlowScope traverseGetElem(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
ObjectType objType = ObjectType.cast(
getJSType
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(n.getFirstChild()).restrictByNotNullOrUndefined());
if (objType != null) {
JSType type = objType.getParameterType();
if (type != null) {
n.setJSType(type);
}
}
return dereferencePointer(n.getFirstChild(), scope);
}
private FlowScope traverseGetProp(Node n, FlowScope scope) {
Node objNode = n.getFirstChild();
Node property = n.getLastChild();
scope = traverseChildren(n, scope);
n.setJSType(
getPropertyType(
objNode.getJSType(), property.getString(), n, scope));
return dereferencePointer(n.getFirstChild(), scope);
}
/**
* Suppose X is an object with inferred properties.
* Suppose also that X is used in a way where it would only type-check
* correctly if some of those properties are widened.
* Then we should be polite and automatically widen X's properties for him.
*
* For a concrete example, consider:
* param x {{prop: (number|undefined)}}
* function f(x) {}
* f({});
*
* If we give the anonymous object an inferred property of (number|undefined),
* then this code will type-check appropriately.
*/
private void inferPropertyTypesToMatchConstraint(
JSType type, JSType constraint) {
ObjectType constraintObj =
ObjectType.cast(constraint.restrictByNotNullOrUndefined());
if (constraintObj != null && constraintObj.isRecordType()) {
ObjectType objType = ObjectType.cast(type.restrictByNotNullOrUndefined());
if (objType != null) {
for (String prop : constraintObj.getOwnPropertyNames()) {
JSType propType = constraintObj.getPropertyType(prop);
if (!objType.isPropertyTypeDeclared(prop)) {
JSType typeToInfer = propType;
if (!objType.hasProperty(prop)) {
typeToInfer =
getNativeType(VOID_TYPE).getLeastSupertype(propType);
}
objType.defineInferredProperty(prop, typeToInfer, null);
}
}
}
}
}
/**
* If we access a property of a symbol, then that symbol is not
* null or undefined.
*/
private FlowScope dereferencePointer(Node n, FlowScope scope) {
if (n.isQualifiedName()) {
JSType type = getJSType(n);
JSType narrowed = type.restrictByNotNullOrUndefined();
if (type != narrowed) {
scope = narrowScope(scope, n, narrowed);
}
}
return scope;
}
private JSType getPropertyType(JSType objType, String propName,
Node n, FlowScope scope) {
// Scopes sometimes contain inferred type info about qualified names.
String qualifiedName = n.getQualifiedName();
StaticSlot<JSType>
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> var = scope.getSlot(qualifiedName);
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
if (varType.equals(getNativeType(UNKNOWN_TYPE)) &&
var != syntacticScope.getSlot(qualifiedName)) {
// If the type of this qualified name has been checked in this scope,
// then use CHECKED_UNKNOWN_TYPE instead to indicate that.
return getNativeType(CHECKED_UNKNOWN_TYPE);
} else {
return varType;
}
}
}
JSType propertyType = null;
if (objType != null) {
propertyType = objType.findPropertyType(propName);
}
if ((propertyType == null || propertyType.isUnknownType()) &&
qualifiedName != null) {
// If we find this node in the registry, then we can infer its type.
ObjectType regType = ObjectType.cast(registry.getType(qualifiedName));
if (regType != null) {
propertyType = regType.getConstructor();
}
}
return propertyType;
}
private BooleanOutcomePair traverseOr(Node n, FlowScope scope) {
return traverseShortCircuitingBinOp(n, scope, false);
}
private BooleanOutcomePair traverseShortCircuitingBinOp(
Node n, FlowScope scope, boolean condition) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
// type the left node
BooleanOutcomePair leftLiterals =
traverseWithinShortCircuitingBinOp(left,
scope.createChildFlowScope());
JSType leftType = left.getJSType();
// reverse abstract interpret the left node to produce the correct
// scope in which to verify the right node
FlowScope rightScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
left, leftLiterals.getOutcomeFlowScope(left.getType(), condition),
condition);
// type the right node
BooleanOutcomePair rightLiterals =
traverseWithinShortCircuitingBinOp(
right, rightScope.createChildFlowScope());
JSType rightType = right.getJSType();
JSType type;
BooleanOutcomePair literals;
if (leftType != null && rightType != null) {
leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!condition);
if (leftLiterals.toBooleanOutcomes ==
BooleanLiteralSet.get(!condition)) {
// Use the restricted left type, since the right side never gets
// evaluated.
type = leftType;
literals = leftLiterals;
} else {
// Use the join of the restricted left type knowing the outcome of the
// ToBoolean predicate and of the right type.
type = leftType.getLeastSupertype(rightType);
literals =
getBooleanOutcomePair(leftLiterals, rightLiterals, condition);
}
// Exclude the boolean type if
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> the literal set is empty because a boolean
// can never actually be returned.
if (literals.booleanValues == BooleanLiteralSet.EMPTY &&
getNativeType(BOOLEAN_TYPE).isSubtype(type)) {
// Exclusion only make sense for a union type.
if (type.isUnionType()) {
type = type.toMaybeUnionType().getRestrictedUnion(
getNativeType(BOOLEAN_TYPE));
}
}
} else {
type = null;
literals = new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH,
leftLiterals.getJoinedFlowScope(),
rightLiterals.getJoinedFlowScope());
}
n.setJSType(type);
return literals;
}
private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n,
FlowScope scope) {
switch (n.getType()) {
case Token.AND:
return traverseAnd(n, scope);
case Token.OR:
return traverseOr(n, scope);
default:
scope = traverse(n, scope);
return newBooleanOutcomePair(n.getJSType(), scope);
}
}
/**
* Infers the boolean outcome pair that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @see #getBooleanOutcomes(BooleanLiteralSet, BooleanLiteralSet, boolean)
*/
BooleanOutcomePair getBooleanOutcomePair(BooleanOutcomePair left,
BooleanOutcomePair right, boolean condition) {
return new BooleanOutcomePair(
getBooleanOutcomes(left.toBooleanOutcomes, right.toBooleanOutcomes,
condition),
getBooleanOutcomes(left.booleanValues, right.booleanValues, condition),
left.getJoinedFlowScope(), right.getJoinedFlowScope());
}
/**
* Infers the boolean literal set that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @param left the set of possible {@code ToBoolean} predicate results for
* the expression on the left side of the operator
* @param right the set of possible {@code ToBoolean} predicate results for
* the expression on the right side of the operator
* @param condition the left side {@code ToBoolean} predicate result that
* causes the right side to get evaluated (i.e. not short-circuited)
* @return a set of possible {@code ToBoolean} predicate results for the
* entire expression
*/
static BooleanLiteralSet getBooleanOutcomes(BooleanLiteralSet left,
BooleanLiteralSet right, boolean condition) {
return right.union(left.intersection(BooleanLiteralSet.get(!condition)));
}
/**
* When traversing short-circuiting binary operations, we need to keep track
* of two sets of boolean literals:
* 1. {@code toBooleanOutcomes}: boolean literals as converted from any types
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.getScope() == syntacticScope;
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
private JSType getNativeType(JSTypeNative typeId) {
return registry.getNativeType(typeId);
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>;
/**
* Callback
*/
public interface Callback {
/**
* <p>Visits a node in pre order (before visiting its children) and decides
* whether this node's children should be traversed. If children are
* traversed, they will be visited by
* {@link #visit(NodeTraversal, Node, Node)} in post order.</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
* @return whether the children of this node should be visited
*/
boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent);
/**
* <p>Visits a node in post order (after its children have been visited).
* A node is visited only if all its parents should be traversed
* ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
*/
void visit(NodeTraversal t, Node n, Node parent);
}
/**
* Callback that also knows about scope changes
*/
public interface ScopedCallback extends Callback {
/**
* Called immediately after entering a new scope. The new scope can
* be accessed through t.getScope()
*/
void enterScope(NodeTraversal t);
/**
* Called immediately before exiting a scope. The ending scope can
* be accessed through t.getScope()
*/
void exitScope(NodeTraversal t);
}
/**
* Abstract callback to visit all nodes in post order.
*/
public abstract static class AbstractPostOrderCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
}
/**
* Abstract scoped callback to visit all nodes in post order.
*/
public abstract static class AbstractScopedCallback
implements ScopedCallback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {}
@Override
public void exitScope(NodeTraversal t) {}
}
/**
* Abstract callback to visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
@Override
public
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.inputId = null;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false,
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
inputId = NodeUtil.getInputId(root);
sourceName = "";
curNode = root;
pushScope(root);
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
inputId = NodeUtil.getInputId(scopeRoot);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
if (n == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
inputId = null;
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.isFunction()) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
}
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may be not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
/**
* Gets the compiler.
*/
public Compiler getCompiler() {
// TODO(nicksantos): Remove this type cast. This is just temporary
// while refactoring.
return (Compiler) compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >=0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
return compiler.getInput(inputId);
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
@SuppressWarnings("fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
inputId = n.getInputId();
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.isFunction());
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.isBlock());
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> != null);
scopes.push(s);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return cfgs.peek();
}
/** Returns the current scope's root. */
public Node getScopeRoot() {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
/**
* Determines whether the traversal is currently in the global scope.
*/
boolean inGlobalScope() {
return getScopeDepth() <= 1;
}
int getScopeDepth() {
return scopes.size() + scopeRoots.size();
}
public boolean hasScope() {
return !(scopes.isEmpty() && scopeRoots.isEmpty());
}
/** Reports a diagnostic (error or warning) */
public void report(Node n, DiagnosticType diagnosticType,
String... arguments) {
JSError error = JSError.make(getSourceName(), n, diagnosticType, arguments);
compiler.report(error);
}
private static String getSourceName(Node n) {
String name = n.getSourceFileName();
return name == null ? "" : name;
}
InputId getInputId() {
return inputId;
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, CheckLevel level, DiagnosticType type,
String... arguments) {
return JSError.make
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(getSourceName(), n, level, type, arguments);
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, DiagnosticType type, String... arguments) {
return JSError.make(getSourceName(), n, type, arguments);
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.graph;
import java.util.List;
/**
* A generic directed graph.
*
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public abstract class DiGraph<N, E> extends Graph<N, E> {
/**
* Gets an immutable iterable over all the nodes in the graph.
*/
public abstract Iterable<DiGraphNode<N, E>> getDirectedGraphNodes();
/**
* Gets an immutable list of out edges of the given node.
*/
public abstract List<DiGraphEdge<N, E>> getOutEdges(N nodeValue);
/**
* Gets an immutable list of in edges of the given node.
*/
public abstract List<DiGraphEdge<N, E>> getInEdges(N nodeValue);
public abstract List<DiGraphNode<N, E>> getDirectedPredNodes(
DiGraphNode<N, E> n);
public abstract List<DiGraphNode<N, E>> getDirectedSuccNodes(
DiGraphNode<N, E> n);
public abstract List<DiGraphNode<N, E>>
getDirectedPredNodes(N nodeValue);
public abstract List<DiGraphNode<N, E>>
getDirectedSuccNodes(N nodeValue);
public abstract DiGraphNode<N, E> createDirectedGraphNode(N nodeValue);
public abstract DiGraphNode<N, E> getDirectedGraphNode(N nodeValue);
public abstract List<DiGraphEdge<N, E>>
getDirectedGraphEdges(N n1, N n2);
/**
* Disconnects all edges from n1 to n2.
*
* @param n1 Source node.
* @param n2 Destination node.
*/
public abstract void disconnectInDirection(N n1, N n2);
/**
* Checks whether two nodes in the graph are connected via a directed edge.
*
* @param n1 Node 1.
* @param n2 Node 2.
* @return <code>true</code> if the graph contains edge from n1 to
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> new TypeInferringCallback(), scopeCreator);
inferTypes.traverseWithScope(node, topScope);
}
void inferTypes(NodeTraversal t, Node n, Scope scope) {
TypeInference typeInference =
new TypeInference(
compiler, computeCfg(n), reverseInterpreter, scope,
assertionFunctionsMap);
try {
typeInference.analyze();
// Resolve any new type names found during the inference.
compiler.getTypeRegistry().resolveTypesInScope(scope);
} catch (DataFlowAnalysis.MaxIterationsExceededException e) {
compiler.report(t.makeError(n, DATAFLOW_ERROR));
}
}
private class TypeInferringCallback implements ScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
Scope scope = t.getScope();
Node node = t.getCurrentNode();
if (scope.isGlobal()) {
inferTypes(t, node, scope);
}
}
@Override
public void exitScope(NodeTraversal t) {
Scope scope = t.getScope();
Node node = t.getCurrentNode();
if (scope.isLocal()) {
inferTypes(t, node, scope);
}
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Do nothing
}
}
private ControlFlowGraph<Node> computeCfg(Node n) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false);
cfa.process(null, n);
return cfa.getCfg();
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> "name {0} is not undefined in the externs.");
static final DiagnosticType INVALID_FUNCTION_DECL =
DiagnosticType.error("JSC_INVALID_FUNCTION_DECL",
"Syntax error: function declaration must have a name");
private CompilerInput synthesizedExternsInput = null;
private Node synthesizedExternsRoot = null;
// Vars that still need to be declared in externs. These will be declared
// at the end of the pass, or when we see the equivalent var declared
// in the normal code.
private final Set<String> varsToDeclareInExterns = Sets.newHashSet();
private final AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
// Whether extern checks emit error.
private final boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
Preconditions.checkState(scriptRoot.isScript());
NodeTraversal t = new NodeTraversal(compiler, this);
// Note we use the global scope to prevent wrong "undefined-var errors" on
// variables that are defined in other js files.
t.traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
// TODO(bashir) Check if we need to createSynthesizedExternVar like process.
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(parent.isFunction());
// A function declaration with an empty name passes Rhino,
// but is supposed to be a syntax error according to the spec.
if (!NodeUtil.isFunctionExpression(parent)) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> t.report(n, INVALID_FUNCTION_DECL);
}
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.isVar() ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
n.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return;
}
// Check module dependencies.
JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (!sanityCheck && scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* Create
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> a new variable in a synthetic script. This will prevent
* subsequent compiler passes from crashing.
*/
private void createSynthesizedExternVar(String varName) {
Node nameNode = Node.newString(Token.NAME, varName);
// Mark the variable as constant if it matches the coding convention
// for constant vars.
// NOTE(nicksantos): honestly, i'm not sure how much this matters.
// AFAIK, all people who use the CONST coding convention also
// compile with undeclaredVars as errors. We have some test
// cases for this configuration though, and it makes them happier.
if (compiler.getCodingConvention().isConstant(varName)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
getSynthesizedExternsRoot().addChildToBack(
new Node(Token.VAR, nameNode));
varsToDeclareInExterns.remove(varName);
compiler.reportCodeChange();
}
/**
* A check for name references in the externs inputs. These used to prevent
* a variable from getting renamed, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.PARAM_LIST:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput == null) {
synthesizedExternsInput =
compiler.newExternInput(SYNTHETIC_VARS_DECLAR);
}
return synthesizedExternsInput;
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> current traversal is in.
*/
private final Deque<BasicBlock> blockStack = new ArrayDeque<BasicBlock>();
/**
* Source of behavior at various points in the traversal.
*/
private final Behavior behavior;
/**
* Javascript compiler to use in traversing.
*/
private final AbstractCompiler compiler;
/**
* Only collect references for filtered variables.
*/
private final Predicate<Var> varFilter;
/**
* Constructor initializes block stack.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior) {
this(compiler, behavior, Predicates.<Var>alwaysTrue());
}
/**
* Constructor only collects references that match the given variable.
*
* The test for Var equality uses reference equality, so it's necessary to
* inject a scope when you traverse.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior,
Predicate<Var> varFilter) {
this.compiler = compiler;
this.behavior = behavior;
this.varFilter = varFilter;
}
/**
* Convenience method for running this pass over a tree with this
* class as a callback.
*/
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
/**
* Same as process but only runs on a part of AST associated to one script.
*/
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
/**
* Gets the variables that were referenced in this callback.
*/
@Override
public Iterable<Var> getAllSymbols() {
return referenceMap.keySet();
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
/**
* Gets the reference collection for the given variable.
*/
@Override
public ReferenceCollection getReferences(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
Var v;
if (n.getString().equals("arguments")) {
v = t.getScope().getArgumentsVar();
} else {
v = t.getScope().getVar(n.getString());
}
if (v != null && varFilter.apply(v)) {
addReference(t, v, new Reference(n, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
blockStack.push(new BasicBlock(parent, n));
}
/**
* Updates block statck and invokes any additional behavior.
*/
@Override
public void exitScope(NodeTraversal t) {
blockStack.pop();
if (t.getScope().isGlobal()) {
// Update global scope reference lists when we are done with it.
compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot());
behavior.afterExitScope(t, compiler.getGlobalVarReferences());
} else {
behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap));
}
}
/**
* Updates block stack.
*/
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return true if this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explcit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.isCase();
}
private void addReference(NodeTraversal t, Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference, t, v);
}
interface ReferenceMap {
ReferenceCollection getReferences(Var var);
}
private static class ReferenceMapWrapper implements ReferenceMap {
private final Map<Var, ReferenceCollection> referenceMap;
public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) {
this.referenceMap = referenceMap;
}
@Override
public ReferenceCollection getReferences(Var var) {
return referenceMap.get(var);
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>parent = parent.getParent();
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.isParamList() &&
grandparent.isFunction();
}
boolean isVarDeclaration() {
return getParent().isVar();
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(getParent());
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
getParent().getType() != Token.VAR ||
nameNode.getFirstChild() != null;
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
Node parent = getParent();
return (parent.isFunction())
? parent : NodeUtil.getAssignedValue(nameNode);
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return getNode().getParent();
}
Node getGrandparent() {
Node parent = getParent();
return parent == null ? null : parent.getParent();
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.isVar()) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
Node parent = getParent();
return parent.isAssign()
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
Node parent = getParent();
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> }
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
@Override
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final Scope outerScope = t.getScope();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.PARAM_LIST:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (parent.getType() != Token.FUNCTION) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.STRING:
// Object literal keys are handled with OBJECTLIT
if (!NodeUtil.isObjectLitKey(n, n.getParent())) {
ensureTyped(t, n, STRING_TYPE);
} else {
// Object literal keys are not typeable
typeable = false;
}
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
// Object literal keys are handled with OBJECTLIT
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.isAssign() &&
parent.getFirstChild() ==
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !NodeUtil.isExpressionNode(parent);
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result =
leftTypeRestricted.testForEquality(rightTypeRestricted);
if (result != TernaryValue.UNKNOWN) {
if (n.getType() == Token.NE) {
result = result.not();
}
report(t, n, DETERMINISTIC_TEST, leftType.toString(),
rightType.toString(), result.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(),
rightType.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
if (rightType.isNumber()) {
validator.expectNumber(
t, n, leftType, "left side of numeric comparison");
} else if (leftType.isNumber()) {
validator.expectNumber(
t, n, rightType, "right side of numeric comparison");
} else if (leftType.matchesNumberContext() &&
rightType.matchesNumberContext()) {
// OK.
} else {
// Whether the comparison is numeric will be determined at runtime
// each time the expression is evaluated. Regardless, both operands
// should match a string context.
String message = "left side of comparison";
validator.expectString(t, n, leftType, message);
validator.expectNotNullOrUndefined(
t, n, leftType, message, getNativeType(STRING_TYPE));
message = "right side of comparison";
validator.expectString(t, n, rightType, message);
validator.expectNotNullOrUndefined(
t, n, rightType, message, getNativeType(STRING_TYPE));
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.IN:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right);
validator.expectObject(t, n, rightType, "'in' requires an object");
validator.expectString(t, left, leftType, "left side of 'in'");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.INSTANCEOF:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right).restrictByNotNullOrUndefined();
validator.expectAnyObject(
t, left, leftType, "deterministic instanceof yields false");
validator.expectActualObject(
t, right, rightType, "instanceof requires an object");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.ASSIGN:
visitAssign(t, n);
typeable = false;
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.ASSIGN_URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(
t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT_CASE:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.isObjectLit())
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
if (n.isObjectLit()) {
for (Node key : n.children()) {
visitObjLitKey(t,
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> key, n);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.isAssign()</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDocInfo info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
if (lvalue.isGetProp()) {
Node object = lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
String property = lvalue.getLastChild().getString();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.isGetProp()) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue);
}
}
// /** @type ... */object.name = ...;
if (info != null && info.hasType()) {
visitAnnotatedAssignGetprop(t, assign,
info.getType().evaluate(t.getScope(), typeRegistry), object,
property, rvalue);
return;
}
checkEnumAlias(t, info, rvalue);
// object.prototype = ...;
if (property.equals("prototype")) {
if (objectJsType != null && objectJsType.isFunctionType()) {
FunctionType functionType = objectJsType.toMaybeFunctionType
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.
// For getter and setter property definitions the
// rvalue type != the property type.
Node rvalue = key.getFirstChild();
JSType rightType = NodeUtil.getObjectLitKeyTypeFromValueType(
key, getJSType(rvalue));
if (rightType == null) {
rightType = getNativeType(UNKNOWN_TYPE);
}
Node owner = objlit;
// Validate value is assignable to the key type.
JSType keyType = getJSType(key);
JSType allowedValueType = keyType;
if (allowedValueType.isEnumElementType()) {
allowedValueType =
allowedValueType.toMaybeEnumElementType().getPrimitiveType();
}
boolean valid = validator.expectCanAssignToPropertyOf(t, key,
rightType, allowedValueType,
owner, NodeUtil.getObjectLitKeyName(key));
if (valid) {
ensureTyped(t, key, rightType);
} else {
ensureTyped(t, key);
}
// Validate that the key type is assignable to the object property type.
// This is necessary as the objlit may have been cast to a non-literal
// object type.
// TODO(johnlenz): consider introducing a CAST node to the AST (or
// perhaps a parentheses node).
JSType objlitType = getJSType(objlit);
ObjectType type = ObjectType.cast(
objlitType.restrictByNotNullOrUndefined());
if (type != null) {
String property = NodeUtil.getObjectLitKeyName(key);
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
t, key, keyType,
type.getPropertyType(property), owner, property);
}
return;
}
}
/**
* Returns true if any type in the chain has an implictCast annotation for
* the given property.
*/
private boolean propertyIsImplicitCast(ObjectType type, String prop) {
for (; type != null; type = type.getImplicitPrototype()) {
JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isImplicitCast()) {
return true;
}
}
return false;
}
/**
* Given a constructor type and a property name, check that the property has
* the JSDoc annotation @override iff the property is declared on a
* superclass. Several checks regarding inheritance correctness are also
* performed.
*/
private void checkDeclaredPropertyInheritance(
NodeTraversal t, Node n, FunctionType ctorType, String propertyName,
JSDocInfo info, JSType propertyType) {
// If the supertype doesn't resolve correctly, we've warned about this
// already.
if (hasUnknownOrEmptySupertype(ctorType)) {
return;
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> FunctionType superClass = ctorType.getSuperClassConstructor();
boolean superClassHasProperty = superClass != null &&
superClass.getPrototype().hasProperty(propertyName);
// For interface
boolean superInterfacesHasProperty = false;
if (ctorType.isInterface()) {
for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) {
superInterfacesHasProperty =
superInterfacesHasProperty || interfaceType.hasProperty(propertyName);
}
}
boolean declaredOverride = info != null && info.isOverride();
boolean foundInterfaceProperty = false;
if (ctorType.isConstructor()) {
for (JSType implementedInterface :
ctorType.getAllImplementedInterfaces()) {
if (implementedInterface.isUnknownType() ||
implementedInterface.isEmptyType()) {
continue;
}
FunctionType interfaceType =
implementedInterface.toObjectType().getConstructor();
Preconditions.checkNotNull(interfaceType);
boolean interfaceHasProperty =
interfaceType.getPrototype().hasProperty(propertyName);
foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty;
if (reportMissingOverride.isOn() && !declaredOverride &&
interfaceHasProperty) {
// @override not present, but the property does override an interface
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_INTERFACE_PROPERTY, propertyName,
interfaceType.getTopMostDefiningType(propertyName).toString()));
}
}
}
if (!declaredOverride && !superClassHasProperty
&& !superInterfacesHasProperty) {
// nothing to do here, it's just a plain new property
return;
}
JSType topInstanceType = superClassHasProperty ?
superClass.getTopMostDefiningType(propertyName) : null;
if (reportMissingOverride.isOn() && ctorType.isConstructor() &&
!declaredOverride && superClassHasProperty) {
// @override not present, but the property does override a superclass
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_SUPERCLASS_PROPERTY, propertyName,
topInstanceType.toString()));
}
if (!declaredOverride) {
// there's no @override to check
return;
}
// @override is present and we have to check that it is ok
if (superClassHasProperty) {
// there is a superclass implementation
JSType superClassPropType =
superClass.getPrototype().getPropertyType(propertyName);
if (!propertyType.canAssignTo(superClassPropType)) {
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superClassPropType.toString(), propertyType.toString()));
}
} else if (superInterfacesHasProperty) {
// there is an super interface property
for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) {
if (interfaceType.hasProperty(propertyName)) {
JSType superPropertyType =
interfaceType
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.getPropertyType(propertyName);
if (!propertyType.canAssignTo(superPropertyType)) {
topInstanceType = interfaceType.getConstructor().
getTopMostDefiningType(propertyName);
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superPropertyType.toString(),
propertyType.toString()));
}
}
}
} else if (!foundInterfaceProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Given a constructor or an interface type, find out whether the unknown
* type is a supertype of the current type.
*/
private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) {
Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface());
Preconditions.checkArgument(!ctor.isUnknownType());
// The type system should notice inheritance cycles on its own
// and break the cycle.
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType() ||
maybeSuperInstanceType.isEmptyType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isOrdinaryFunction() &&
!(rvalue.isQualifiedName() &&
rvalue.getQualifiedName().equals(abstractMethodName))) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().isFunction()
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object,
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* object.property = ...;
* </pre>
* that have an {@code @type} annotation.
*/
private void visitAnnotatedAssignGetprop(NodeTraversal t,
Node assign, JSType type, Node object, String property, Node rvalue) {
// verifying that the rvalue has the correct type
validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type,
object, property);
}
/**
* Visits a NAME node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore. Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.PARAM_LIST ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// GETPROP nodes have an assigned type on their node by the scope creator
// if this is an enum declaration. The only namespaced enum declarations
// that we allow are of the form object.name = ...;
if (n.getJSType() != null && parent.isAssign()) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
return;
}
// obj.prop or obj.method()
// Lots of types can appear on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
// TODO(user): remove in favor of flagging every property access on
// non-object.
if (!validator.expectNotNullOrUndefined(t, n, childType,
"No properties on this expression", getNativeType(OBJECT_TYPE))) {
ensureTyped(t, n);
return;
}
checkPropertyAccess(childType, property.getString(), t, n);
ensureTyped(t, n);
}
/**
* Emit a warning if we can prove that a property cannot possibly be
* defined on an object. Note the difference between JS and a strictly
* statically typed language: we're checking if the property
* *cannot be defined*, whereas a java compiler would check if the
* property *can be undefined*.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t, Node n) {
// If the property type is unknown, check the object type to see if it
// can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for
// properties where we've checked that it exists, or for properties on
// objects that aren't in this binary).
JSType propType = getJSType(n);
if (propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
childType = childType.autobox();
ObjectType objectType = ObjectType.cast(childType);
if (objectType != null) {
// We special-case object types so that checks on enums can be
// much stricter, and so that we can use hasProperty (which is much
// faster in most cases).
if (!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else {
checkPropertyAccessHelper(objectType, propName, t, n);
}
}
} else {
checkPropertyAccessHelper(childType, propName, t, n);
}
}
}
private void checkPropertyAccessHelper(JSType objectType, String propName,
NodeTraversal t, Node n) {
if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirst
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Child(), true));
}
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().isOr() &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
validator.expectIndexMatch(t, n, getJSType(left), getJSType(right));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
checkEnumAlias(t, info, value);
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
JSType type = getJSType(constructor).restrictByNotNullOrUndefined();
if (type.isConstructor() || type.isEmptyType() || type.isUnknownType()) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null) {
visitParameterList(t, n, fnType);
ensureTyped(t, n, fnType.getInstanceType());
} else {
ensureTyped(t, n);
}
} else {
report(t, n, NOT_A_CONSTRUCTOR);
ensureTyped(t, n);
}
}
/**
* Check whether there's any property conflict for for a particular super
* interface
* @param t The node traversal object that supplies context
* @param n The node being visited
* @param functionName The function name being checked
* @param properties The property names in the super interfaces that have
* been visited
* @param currentProperties The property names in the super interface
* that have been visited
* @param interfaceType The super interface that is being visited
*/
private void checkInterfaceConflictProperties(NodeTraversal t, Node n,
String functionName, HashMap<String, ObjectType> properties,
HashMap<String, ObjectType> currentProperties,
ObjectType interfaceType) {
Set<String> currentPropertyNames = interfaceType.getPropertyNames();
for (String name : currentPropertyNames) {
ObjectType oType = properties.get(name);
if (oType != null) {
if (!interfaceType.getPropertyType(name).isEquivalentTo(
oType.getPropertyType(name))) {
compiler.report(
t.makeError(n, INCOMPATIBLE_EXTENDED_PROPERTY_TYPE,
functionName, name, oType.toString(),
interfaceType.toString()));
}
}
currentProperties.put(name, interfaceType);
}
for (ObjectType iType : interfaceType.getCtorExtendedInterfaces()) {
checkInterfaceConflictProperties(t, n, functionName, properties,
currentProperties, iType);
}
}
/**
* Visits a {@link Token#FUNCTION} node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType functionType = JSType.toMaybeFunctionType(n.getJSType());
String functionPrivateName =
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> n.getFirstChild().getString();
if (functionType.isConstructor()) {
FunctionType baseConstructor = functionType.getSuperClassConstructor();
if (baseConstructor != null &&
baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) &&
(baseConstructor.isInterface() && functionType.isConstructor())) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName));
} else {
// All interfaces are properly implemented by a class
for (JSType baseInterface : functionType.getImplementedInterfaces()) {
boolean badImplementedType = false;
ObjectType baseInterfaceObj = ObjectType.cast(baseInterface);
if (baseInterfaceObj != null) {
FunctionType interfaceConstructor =
baseInterfaceObj.getConstructor();
if (interfaceConstructor != null &&
!interfaceConstructor.isInterface()) {
badImplementedType = true;
}
} else {
badImplementedType = true;
}
if (badImplementedType) {
report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName);
}
}
// check properties
validator.expectAllInterfaceProperties(t, n, functionType);
}
} else if (functionType.isInterface()) {
// Interface must extend only interfaces
for (ObjectType extInterface : functionType.getExtendedInterfaces()) {
if (extInterface.getConstructor() != null
&& !extInterface.getConstructor().isInterface()) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName));
}
}
// Interface cannot implement any interfaces
if (functionType.hasImplementedInterfaces()) {
compiler.report(t.makeError(n,
CONFLICTING_IMPLEMENTED_TYPE, functionPrivateName));
}
// Check whether the extended interfaces have any conflicts
if (functionType.getExtendedInterfacesCount() > 1) {
// Only check when extending more than one interfaces
HashMap<String, ObjectType> properties
= new HashMap<String, ObjectType>();
HashMap<String, ObjectType> currentProperties
= new HashMap<String, ObjectType>();
for (ObjectType interfaceType : functionType.getExtendedInterfaces()) {
currentProperties.clear();
checkInterfaceConflictProperties(t, n, functionPrivateName,
properties, currentProperties, interfaceType);
properties.putAll(currentProperties);
}
}
}
}
/**
* Visits a CALL node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitCall(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOrUndefined();
if (!childType.canBeCalled()) {
report(t,
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> n, NOT_CALLABLE, childType.toString());
ensureTyped(t, n);
return;
}
// A couple of types can be called as if they were functions.
// If it is a function type, then validate parameters.
if (childType.isFunctionType()) {
FunctionType functionType = childType.toMaybeFunctionType();
boolean isExtern = false;
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if( functionJSDocInfo != null &&
functionJSDocInfo.getAssociatedNode() != null) {
isExtern = functionJSDocInfo.getAssociatedNode().isFromExterns();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.isGetElem() ||
child.isGetProp())) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
private void visitParameterList(NodeTraversal t, Node call,
FunctionType functionType) {
Iterator<Node> arguments = call.children().iterator();
arguments.next(); // skip the function name
Iterator<Node> parameters = functionType.getParameters().iterator();
int ordinal = 0;
Node parameter = null;
Node argument = null;
while (arguments.hasNext() &&
(parameters.hasNext() ||
parameter != null && parameter.isVarArgs())) {
// If there are no parameters left in the list, then the while loop
// above implies that this must be a var_args function.
if (parameters.hasNext()) {
parameter = parameters.next();
}
argument = arguments.next();
ordinal++;
validator.expectArgumentMatchesParameter(t, argument,
getJSType(
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>argument), getJSType(parameter), call, ordinal);
}
int numArgs = call.getChildCount() - 1;
int minArgs = functionType.getMinArguments();
int maxArgs = functionType.getMaxArguments();
if (minArgs > numArgs || maxArgs < numArgs) {
report(t, call, WRONG_ARGUMENT_COUNT,
validator.getReadableJSTypeName(call.getFirstChild(), false),
String.valueOf(numArgs), String.valueOf(minArgs),
maxArgs != Integer.MAX_VALUE ?
" and no more than " + maxArgs + " argument(s)" : "");
}
}
/**
* Visits a RETURN node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitReturn(NodeTraversal t, Node n) {
Node function = t.getEnclosingFunction();
// This is a misplaced return, but the real JS will fail to compile,
// so let it go.
if (function == null) {
return;
}
JSType jsType = getJSType(function);
if (jsType.isFunctionType()) {
FunctionType functionType = jsType.toMaybeFunctionType();
JSType returnType = functionType.getReturnType();
// if no return type is specified, undefined must be returned
// (it's a void function)
if (returnType == null) {
returnType = getNativeType(VOID_TYPE);
}
// fetching the returned value's type
Node valueNode = n.getFirstChild();
JSType actualReturnType;
if (valueNode == null) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
validator.expectBitwiseable(t, left, leftType,
"bad left operand to bitwise operator");
validator.expectBitwiseable(t, right, rightType,
"bad right operand to bitwise operator");
break;
case Token.ASSIGN_ADD:
case Token.ADD:
break;
default:
report(t, n, UNEXPECTED_TOKEN, Node.tokenToName(op));
}
ensureTyped(t, n);
}
/**
* <p>Checks enum aliases.
*
* <p>We verify that the enum element type of the enum used
* for initialization is a subtype of the enum element type of
* the enum the value is being copied in.</p>
*
* <p>Example:</p>
* <pre>var myEnum = myOtherEnum;</pre>
*
* <p>Enum aliases are irregular, so we need special code for this :(</p>
*
* @param value the value used for initialization of the enum
*/
private void checkEnumAlias(
NodeTraversal t, JSDocInfo declInfo, Node value) {
if (declInfo == null || !declInfo.hasEnumParameterType()) {
return;
}
JSType valueType = getJSType(value);
if (!valueType.isEnumType()) {
return;
}
EnumType valueEnumType = valueType.toMaybeEnumType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
declInfo.getEnumParameterType().evaluate(t.getScope(), typeRegistry),
"incompatible enum element types");
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
// TODO(nicksantos): TypeCheck should never be attaching types to nodes.
// All types should be attached by TypeInference. This is not true today
// for legacy reasons. There are a number of places where TypeInference
// doesn't attach a type, as a signal to TypeCheck that it needs to check
// that node's type.
/**
* Ensure that the given node has a type. If it does not have one,
* attach the UNKNOWN_TYPE.
*/
private void ensureTyped(NodeTraversal t, Node n) {
ensureTyped(t, n, getNativeType(UNKNOWN_TYPE));
}
private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) {
ensureTyped(t, n, getNativeType(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(n.getType() != Token.FUNCTION ||
type.isFunctionType() ||
type.isUnknownType());
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry);
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns) {
String propName = n
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.isGetProp() ?
n.getLastChild().getString() : "(missing)";
compiler.report(
t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName));
}
}
if (n.getJSType() == null) {
n.setJSType(type);
}
}
/**
* Returns the percentage of nodes typed by the type checker.
* @return a number between 0.0 and 100.0
*/
double getTypedPercent() {
int total = nullCount + unknownCount + typedCount;
if (total == 0) {
return 0.0;
} else {
return (100.0 * typedCount) / total;
}
}
private JSType getNativeType(JSTypeNative typeId) {
return typeRegistry.getNativeType(typeId);
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.regex.RegExpTree;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable, and checks that regular expressions
* are syntactically valid.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning(
"JSC_MALFORMED_REGEXP",
"Malformed Regular Expression: {0}");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
// Check the syntax of regular expression patterns.
} else if (n.getType() == Token.REGEXP) {
String pattern = n.getFirstChild().getString
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>();
String flags = n.getChildCount() == 2
? n.getLastChild().getString() : "";
try {
RegExpTree.parseRegExp(pattern, flags);
} catch (IllegalArgumentException ex) {
t.report(n, MALFORMED_REGEXP, ex.getMessage());
}
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
maybeSetLengthFrom(irNode, node);
}
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
* @param node The JsDoc Comment node to parse.
* @param irNode
* @return A JSDocInfoParser. Will contain either fileoverview jsdoc, or
* normal jsdoc, or no jsdoc (if the method parses to the wrong level).
*/
private JsDocInfoParser createJsDocInfoParser(Comment node, Node irNode) {
String comment = node.getValue();
int lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser jsdocParser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
node,
irNode,
config,
errorReporter);
jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder);
jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
// Set the length on the node if we're in IDE mode.
private void maybeSetLengthFrom(Node node, AstNode source) {
if (config.isIdeMode) {
node.setLength(source.getLength());
}
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.jscomp.mozilla.rhino.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.jscomp.mozilla.rhino.Node child : n) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> node.addChildToBack(transform((AstNode)child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token.STRING, then quotes it.
* Used for properties, as the old AST uses String tokens, while the new one
* uses Name tokens for unquoted strings. For example, in
* var o = {'a' : 1, b: 2};
* the string 'a' is quoted, while the name b is turned into a string, but
* unquoted.
*/
private Node transformAsString(AstNode n) {
Node ret;
if (n instanceof Name) {
ret = transformNameAsString((Name)n);
} else if (n instanceof NumberLiteral) {
ret = transformNumberAsString((NumberLiteral)n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
} else {
ret = transform(n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
}
Preconditions.checkState(ret.isString());
return ret;
}
@Override
Node processArrayLiteral(ArrayLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.ARRAYLIT);
for (AstNode child : literalNode.getElements()) {
Node c = transform(child);
node.addChildToBack(c);
}
return node;
}
@Override
Node processAssignment(Assignment assignmentNode) {
Node assign = processInfixExpression(assignmentNode);
Node target = assign.getFirstChild();
if (!validAssignmentTarget(target)) {
errorReporter.error(
"invalid assignment target",
sourceName,
target.getLineno(), "", 0);
}
return assign;
}
@Override
Node processAstRoot(AstRoot rootNode) {
Node node = newNode(Token.SCRIPT);
for (com.google.javascript.jscomp.mozilla.rhino.Node child : rootNode) {
node.addChildToBack(transform((AstNode)child));
}
parseDirectives(node);
return node;
}
/**
* Parse the directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* Ecma-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.remove
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>FirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) return false;
int nType = n.getType();
return nType == Token.EXPR_RESULT &&
n.getFirstChild().isString() &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
errorReporter.error(
"Catch clauses are not supported",
sourceName,
clauseNode.getCatchCondition().getLineno(), "", 0);
}
node.addChildToBack(transformBlock(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode) {
return newNode(
Token.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = newNode(Token.CONTINUE);
if (statementNode.getLabel() != null) {
Node labelName = transform(statementNode.getLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return newNode(
Token.DO,
transformBlock(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
return newNode(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processExpressionStatement(ExpressionStatement statementNode) {
Node node = newNode(transformTokenType(statementNode.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> there's no
// function name, assume the paren was on the same line as the function.
// TODO(bowdidge): Mark line number of paren correctly.
Name fnName = functionNode.getFunctionName();
if (fnName != null) {
lp.setLineno(fnName.getLineno());
} else {
lp.setLineno(functionNode.getLineno());
}
int lparenCharno = functionNode.getLp() +
functionNode.getAbsolutePosition();
lp.setCharno(position2charno(lparenCharno));
for (AstNode param : functionNode.getParams()) {
lp.addChildToBack(transform(param));
}
node.addChildToBack(lp);
Node bodyNode = transform(functionNode.getBody());
parseDirectives(bodyNode);
node.addChildToBack(bodyNode);
return node;
}
@Override
Node processIfStatement(IfStatement statementNode) {
Node node = newNode(Token.IF);
node.addChildToBack(transform(statementNode.getCondition()));
node.addChildToBack(transformBlock(statementNode.getThenPart()));
if (statementNode.getElsePart() != null) {
node.addChildToBack(transformBlock(statementNode.getElsePart()));
}
return node;
}
@Override
Node processInfixExpression(InfixExpression exprNode) {
Node n = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getLeft()),
transform(exprNode.getRight()));
n.setLineno(exprNode.getLineno());
n.setCharno(position2charno(exprNode.getAbsolutePosition()));
maybeSetLengthFrom(n, exprNode);
return n;
}
@Override
Node processKeywordLiteral(KeywordLiteral literalNode) {
return newNode(transformTokenType(literalNode.getType()));
}
@Override
Node processLabel(Label labelNode) {
return newStringNode(Token.LABEL_NAME, labelNode.getName());
}
@Override
Node processLabeledStatement(LabeledStatement statementNode) {
Node node = newNode(Token.LABEL);
Node prev = null;
Node cur = node;
for (Label label : statementNode.getLabels()) {
if (prev != null) {
prev.addChildToBack(cur);
}
cur.addChildToBack(transform(label));
cur.setLineno(label.getLineno());
maybeSetLengthFrom(cur, label);
int clauseAbsolutePosition =
position2charno(label.getAbsolutePosition());
cur.setCharno(clauseAbsolutePosition);
prev = cur;
cur = newNode(Token.LABEL);
}
prev.addChildToBack(transform(statementNode.getStatement()));
return node;
}
@Override
Node processName(Name nameNode) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>, Boolean.TRUE);
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
return newNode(
Token.GETPROP,
transform(getNode.getTarget()),
transformAsString(getNode.getProperty()));
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = newStringNode(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
maybeSetLengthFrom(literalStringNode, literalNode);
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = newStringNode(flags);
// Assume the flags are on the same line as the literal node.
flagsNode.setLineno(literalNode.getLineno());
maybeSetLengthFrom(flagsNode, literalNode);
node.addChildToBack(flagsNode);
}
return node;
}
@Override
Node processReturnStatement(ReturnStatement statementNode) {
Node node = newNode(Token.RETURN);
if (statementNode.getReturnValue() != null) {
node.addChildToBack(transform(statementNode.getReturnValue()));
}
return node;
}
@Override
Node processScope(Scope scopeNode) {
return processGeneric(scopeNode);
}
@Override
Node processStringLiteral(StringLiteral literalNode) {
Node n = newStringNode(literalNode.getValue());
return n;
}
@Override
Node processSwitchCase(SwitchCase caseNode) {
Node node;
if (caseNode.isDefault()) {
node = newNode(Token.DEFAULT_CASE);
} else {
AstNode expr = caseNode.getExpression();
node = newNode(Token.CASE, transform(expr));
}
Node block = newNode(Token.BLOCK);
block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
block.setLineno(caseNode.getLineno());
block.setCharno(position2charno(caseNode.getAbsolutePosition()));
maybeSetLengthFrom(block, caseNode);
if (caseNode.getStatements() != null) {
for (AstNode child : caseNode.getStatements()) {
block.addChildToBack(transform(child));
}
}
node.addChildToBack(block);
return node;
}
@Override
Node processSwitchStatement(SwitchStatement statementNode) {
Node node = newNode(Token.SWITCH,
transform(statementNode.getExpression()));
for (AstNode child : statementNode.getCases()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processThrowStatement(ThrowStatement statementNode) {
return newNode(Token.THROW,
transform(statementNode
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> old roots exist (we are parsing a second time), detach each of the
// individual file parse trees.
if (externsRoot != null) {
externsRoot.detachChildren();
}
if (jsRoot != null) {
jsRoot.detachChildren();
}
// Parse main js sources.
jsRoot = new Node(Token.BLOCK);
jsRoot.setIsSyntheticBlock(true);
externsRoot = new Node(Token.BLOCK);
externsRoot.setIsSyntheticBlock(true);
externAndJsRoot = new Node(Token.BLOCK, externsRoot, jsRoot);
externAndJsRoot.setIsSyntheticBlock(true);
if (options.tracer.isOn()) {
tracker = new PerformanceTracker(jsRoot,
options.tracer == TracerMode.ALL);
addChangeHandler(tracker.getCodeChangeHandler());
}
Tracer tracer = newTracer("parseInputs");
try {
// Parse externs sources.
for (CompilerInput input : externs) {
Node n = input.getAstRoot(this);
if (hasErrors()) {
return null;
}
externsRoot.addChildToBack(n);
}
// Check if the sources need to be re-ordered.
if (options.manageClosureDependencies) {
for (CompilerInput input : inputs) {
input.setCompiler(this);
// Forward-declare all the provided types, so that they
// are not flagged even if they are dropped from the process.
for (String provide : input.getProvides()) {
getTypeRegistry().forwardDeclareType(provide);
}
}
try {
inputs =
(moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph)
.manageDependencies(
options.manageClosureDependenciesEntryPoints, inputs);
} catch (CircularDependencyException e) {
report(JSError.make(
JSModule.CIRCULAR_DEPENDENCY_ERROR, e.getMessage()));
return null;
} catch (MissingProvideException e) {
report(JSError.make(
MISSING_ENTRY_ERROR, e.getMessage()));
return null;
}
}
// Check if inputs need to be rebuilt from modules.
boolean staleInputs = false;
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
// Inputs can have a null AST during initial parse.
if (n == null) {
continue;
}
if (n.getJSDocInfo() != null) {
JSDocInfo info = n.getJSDocInfo();
if (info.isExterns()) {
// If the input file is explicitly marked as an externs file, then
// assume the programmer made a mistake and throw it into
// the externs pile anyways.
externsRoot.addChildToBack(n);
input.setIsExtern(true);
input.getModule
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>().remove(input);
externs.add(input);
staleInputs = true;
} else if (info.isNoCompile()) {
input.getModule().remove(input);
staleInputs = true;
}
}
}
if (staleInputs) {
fillEmptyModules(modules);
rebuildInputsFromModules();
}
// Build the AST.
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
if (n == null) {
continue;
}
if (devMode) {
runSanityCheck();
if (hasErrors()) {
return null;
}
}
if (options.sourceMapOutputPath != null ||
options.nameReferenceReportPath != null) {
// Annotate the nodes in the tree with information from the
// input file. This information is used to construct the SourceMap.
SourceInformationAnnotator sia =
new SourceInformationAnnotator(
input.getName(), options.devMode != DevMode.OFF);
NodeTraversal.traverse(this, n, sia);
}
jsRoot.addChildToBack(n);
}
if (hasErrors()) {
return null;
}
return externAndJsRoot;
} finally {
stopTracer(tracer, "parseInputs");
}
}
public Node parse(JSSourceFile file) {
initCompilerOptionsIfTesting();
addToDebugLog("Parsing: " + file.getName());
return new JsAst(file).getAstRoot(this);
}
private int syntheticCodeId = 0;
@Override
Node parseSyntheticCode(String js) {
CompilerInput input = new CompilerInput(
JSSourceFile.fromCode(" [synthetic:" + (++syntheticCodeId) + "] ", js));
inputsById.put(input.getInputId(), input);
return input.getAstRoot(this);
}
void initCompilerOptionsIfTesting() {
if (options == null) {
// initialization for tests that don't initialize the compiler
// by the normal mechanisms.
initOptions(new CompilerOptions());
}
}
@Override
Node parseSyntheticCode(String fileName, String js) {
initCompilerOptionsIfTesting();
return parse(JSSourceFile.fromCode(fileName, js));
}
@Override
Node parseTestCode(String js) {
initCompilerOptionsIfTesting();
CompilerInput input = new CompilerInput(
JSSourceFile.fromCode(" [testcode] ", js));
if (inputsById == null) {
inputsById = Maps.newHashMap();
}
inputsById.put(input.getInputId(), input);
return input.getAstRoot(this);
}
@Override
ErrorReporter getDefaultErrorReporter() {
return defaultErrorReporter;
}
//------------------------------------------------------------------------
// Convert back to source code
//------------------------------------------------------------------------
/**
* Converts the main parse
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>length();
char lastChar = code.charAt(length - 1);
char secondLastChar = length >= 2 ?
code.charAt(length - 2) : '\0';
boolean hasSemiColon = lastChar == ';' ||
(lastChar == '\n' && secondLastChar == ';');
if (!hasSemiColon) {
cb.append(";");
}
}
return null;
}
});
}
/**
* Generates JavaScript source code for an AST, doesn't generate source
* map info.
*/
@Override
String toSource(Node n) {
initCompilerOptionsIfTesting();
return toSource(n, null, true);
}
/**
* Generates JavaScript source code for an AST.
*/
private String toSource(Node n, SourceMap sourceMap, boolean firstOutput) {
CodePrinter.Builder builder = new CodePrinter.Builder(n);
builder.setPrettyPrint(options.prettyPrint);
builder.setLineBreak(options.lineBreak);
builder.setSourceMap(sourceMap);
builder.setSourceMapDetailLevel(options.sourceMapDetailLevel);
builder.setTagAsStrict(firstOutput &&
options.getLanguageOut() == LanguageMode.ECMASCRIPT5_STRICT);
builder.setLineLengthThreshold(options.lineLengthThreshold);
Charset charset = options.outputCharset != null ?
Charset.forName(options.outputCharset) : null;
builder.setOutputCharset(charset);
return builder.build();
}
/**
* Stores a buffer of text to which more can be appended. This is just like a
* StringBuilder except that we also track the number of lines.
*/
public static class CodeBuilder {
private final StringBuilder sb = new StringBuilder();
private int lineCount = 0;
private int colCount = 0;
/** Removes all text, but leaves the line count unchanged. */
void reset() {
sb.setLength(0);
}
/** Appends the given string to the text buffer. */
CodeBuilder append(String str) {
sb.append(str);
// Adjust the line and column information for the new text.
int index = -1;
int lastIndex = index;
while ((index = str.indexOf('\n', index + 1)) >= 0) {
++lineCount;
lastIndex = index;
}
if (lastIndex == -1) {
// No new lines, append the new characters added.
colCount += str.length();
} else {
colCount = str.length() - (lastIndex + 1);
}
return this;
}
/** Returns all text in the text buffer. */
@Override
public String toString() {
return sb.toString();
}
/** Returns the length of the text buffer. */
public int getLength() {
return sb.length();
}
/** Returns the (zero-
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> {
return false;
}
/**
* A null-safe version of JSType#toObjectType.
*/
public static ObjectType cast(JSType type) {
return type == null ? null : type.toObjectType();
}
@Override
public final boolean isFunctionPrototypeType() {
return getOwnerFunction() != null;
}
/** Gets the owner of this if it's a function prototype. */
public FunctionType getOwnerFunction() {
return null;
}
/**
* Gets the interfaces implemented by the ctor associated with this type.
* Intended to be overridden by subclasses.
*/
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return ImmutableSet.of();
}
/**
* Gets the interfaces extended by the interface associated with this type.
* Intended to be overriden by subclasses.
*/
public Iterable<ObjectType> getCtorExtendedInterfaces() {
return ImmutableSet.of();
}
public static final class Property
implements Serializable, StaticSlot<JSType>, StaticReference<JSType> {
private static final long serialVersionUID = 1L;
/**
* Property's name.
*/
private final String name;
/**
* Property's type.
*/
private JSType type;
/**
* Whether the property's type is inferred.
*/
private final boolean inferred;
/**
* The node corresponding to this property, e.g., a GETPROP node that
* declares this property.
*/
private Node propertyNode;
/** The JSDocInfo for this property. */
private JSDocInfo docInfo = null;
Property(String name, JSType type, boolean inferred,
Node propertyNode) {
this.name = name;
this.type = type;
this.inferred = inferred;
this.propertyNode = propertyNode;
}
@Override
public String getName() {
return name;
}
@Override
public Node getNode() {
return propertyNode;
}
@Override
public StaticSourceFile getSourceFile() {
return propertyNode == null ? null : propertyNode.getStaticSourceFile();
}
@Override
public Property getSymbol() {
return this;
}
@Override
public Property getDeclaration() {
return propertyNode == null ? null : this;
}
@Override
public JSType getType() {
return type;
}
@Override
public boolean isTypeInferred() {
return inferred;
}
boolean isFromExterns() {
return propertyNode == null ? false : propertyNode.isFromExterns();
}
void setType(JSType type) {
this.type = type;
}
@Override public JSDocInfo getJSDocInfo() {
return this.docInfo;
}
void setJSDocInfo(JSDocInfo info) {
this.docInfo = info;
}
public void setNode(Node n) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
this.propertyNode = n;
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* <p>The syntactic scope creator scans the parse tree to create a Scope object
* containing all the variable declarations in that scope.</p>
*
* <p>This implementation is not thread-safe.</p>
*
*/
class SyntacticScopeCreator implements ScopeCreator {
private final AbstractCompiler compiler;
private Scope scope;
private InputId inputId;
private final RedeclarationHandler redeclarationHandler;
// The arguments variable is special, in that it's declared in every local
// scope, but not explicitly declared.
private static final String ARGUMENTS = "arguments";
public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR =
DiagnosticType.error(
"JSC_VAR_MULTIPLY_DECLARED_ERROR",
"Variable {0} first declared in {1}");
public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR =
DiagnosticType.error(
"JSC_VAR_ARGUMENTS_SHADOWED_ERROR",
"Shadowing \"arguments\" is not allowed");
/**
* Creates a ScopeCreator.
*/
SyntacticScopeCreator(AbstractCompiler compiler) {
this.compiler = compiler;
this.redeclarationHandler = new DefaultRedeclarationHandler();
}
SyntacticScopeCreator(
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
@Override
public Scope createScope(Node n, Scope parent) {
inputId = null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
inputId = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
if (n.isFunction()) {
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
// TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
// from the AST.
// Is it meaningful to build a scope for detached FUNCTION node?
}
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.isParamList());
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.isName());
declareVar(a);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().isName());
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
inputId = n.getInputId();
Preconditions.checkNotNull(inputId);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.isCatch() &&
parent.isCatch()) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar);
if (!allowDupe) {
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.isName());
CompilerInput input = compiler.getInput(inputId);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* @param n The name node to check.
* @param origVar The associated Var.
* @return Whether duplicated declarations warnings should be suppressed
* for the given node.
*/
static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Node origParent = origVar.getParentNode();
JSDocInfo info = n.getJSDocInfo();
if (info == null
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> escaped in a
* dataflow analysis, it can be reference outside of the code that we are
* analyzing. A variable is escaped if any of the following is true:
*
* <p><ol>
* <li>It is defined as the exception name in CATCH clause so it became a
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in javascript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow.</li>
*/
static void computeEscaped(final Scope jsScope, final Set<Var> escaped,
AbstractCompiler compiler) {
// TODO(user): Very good place to store this information somewhere.
AbstractPostOrderCallback finder = new AbstractPostOrderCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (jsScope == t.getScope() || !n.isName()
|| parent.isFunction()) {
return;
}
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null && var.scope == jsScope) {
escaped.add(jsScope.getVar(name));
}
}
};
NodeTraversal t = new NodeTraversal(compiler, finder);
t.traverseAtScope(jsScope);
// 1: Remove the exception name in CATCH which technically isn't local to
// begin with.
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var var = i.next();
if (var.getParentNode().isCatch() ||
compiler.getCodingConvention().isExported(var.getName())) {
escaped.add(var);
}
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2011 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* {@link CheckDebuggerStatement} checks for the presence of the "debugger"
* statement in JavaScript code. It is appropriate to use this statement while
* developing JavaScript; however, it is generally undesirable to include it in
* production code.
*
* @author bolinfest@google.com (Michael Bolin)
*/
class CheckDebuggerStatement extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType DEBUGGER_STATEMENT_PRESENT =
DiagnosticType.disabled("JSC_DEBUGGER_STATEMENT_PRESENT",
"Using the debugger statement can halt your application if the user " +
"has a JavaScript debugger running.");
private final AbstractCompiler compiler;
public CheckDebuggerStatement(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.DEBUGGER) {
t.report(n, DEBUGGER_STATEMENT_PRESENT);
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> final DiagnosticType ILLEGAL_NAME = DiagnosticType.error(
"JSC_ILLEGAL_NAME",
"identifiers ending in '__' cannot be used in Caja");
static final DiagnosticType DUPLICATE_OBJECT_KEY = DiagnosticType.warning(
"JSC_DUPLICATE_OBJECT_KEY",
"object literals cannot contain duplicate keys in ES5 strict mode");
private final AbstractCompiler compiler;
private final boolean noVarCheck;
private final boolean noCajaChecks;
StrictModeCheck(AbstractCompiler compiler) {
this(compiler, false, false);
}
StrictModeCheck(
AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) {
this.compiler = compiler;
this.noVarCheck = noVarCheck;
this.noCajaChecks = noCajaChecks;
}
@Override public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
NodeTraversal.traverse(compiler, root, new NonExternChecks());
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
if (!isDeclaration(n)) {
checkNameUse(t, n);
}
} else if (n.isAssign()) {
checkAssignment(t, n);
} else if (n.getType() == Token.DELPROP) {
checkDelete(t, n);
} else if (n.isObjectLit()) {
checkObjectLiteral(t, n);
} else if (n.isLabel()) {
checkLabel(t, n);
}
}
/**
* Determines if the given name is a declaration, which can be a declaration
* of a variable, function, or argument.
*/
private static boolean isDeclaration(Node n) {
switch (n.getParent().getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.CATCH:
return true;
case Token.PARAM_LIST:
return n.getParent().getParent().isFunction();
default:
return false;
}
}
/** Checks that the given name is used legally. */
private void checkNameUse(NodeTraversal t, Node n) {
Var v = t.getScope().getVar(n.getString());
if (v == null) {
// In particular, this prevents creating a global variable by assigning
// to it without a declaration.
if (!noVarCheck) {
t.report(n, UNKNOWN_VARIABLE, n.getString());
}
}
if (!noCajaChecks) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_USE);
} else if (n.getString().endsWith("__")) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks that
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> an assignment is not to the "arguments" object. */
private void checkAssignment(NodeTraversal t, Node n) {
if (n.getFirstChild().isName()) {
if ("arguments".equals(n.getFirstChild().getString())) {
t.report(n, ARGUMENTS_ASSIGNMENT);
} else if ("eval".equals(n.getFirstChild().getString())) {
// Note that assignment to eval is already illegal because any use of
// that name is illegal.
if (noCajaChecks) {
t.report(n, EVAL_ASSIGNMENT);
}
}
}
}
/** Checks that variables, functions, and arguments are not deleted. */
private void checkDelete(NodeTraversal t, Node n) {
if (n.getFirstChild().isName()) {
Var v = t.getScope().getVar(n.getFirstChild().getString());
if (v != null) {
t.report(n, DELETE_VARIABLE);
}
}
}
/** Checks that object literal keys are valid. */
private void checkObjectLiteral(NodeTraversal t, Node n) {
Set<String> getters = Sets.newHashSet();
Set<String> setters = Sets.newHashSet();
for (Node key = n.getFirstChild();
key != null;
key = key.getNext()) {
if (!noCajaChecks && key.getString().endsWith("__")) {
t.report(key, ILLEGAL_NAME);
}
if (key.getType() != Token.SETTER_DEF) {
// normal property and getter cases
if (getters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
getters.add(key.getString());
}
}
if (key.getType() != Token.GETTER_DEF) {
// normal property and setter cases
if (setters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
setters.add(key.getString());
}
}
}
}
/** Checks that label names are valid. */
private void checkLabel(NodeTraversal t, Node n) {
if (n.getFirstChild().getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n.getFirstChild(), ILLEGAL_NAME);
}
}
}
/** Checks that are performed on non-extern code only. */
private class NonExternChecks extends AbstractPostOrderCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if ((n.isName()) && isDeclaration(n)) {
checkDeclaration(t, n);
} else if (n.isGetProp()) {
checkProperty(t, n);
}
}
/** Checks for illegal declarations. */
private void checkDeclaration
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(NodeTraversal t, Node n) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_DECLARATION);
} else if ("arguments".equals(n.getString())) {
t.report(n, ARGUMENTS_DECLARATION);
} else if (n.getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks for illegal property accesses. */
private void checkProperty(NodeTraversal t, Node n) {
if (n.getLastChild().getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n.getLastChild(), ILLEGAL_NAME);
}
}
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
@Override
public void process(Node externs, Node root) {
for (TypeMismatch mis : compiler.getTypeValidator().getMismatches()) {
addInvalidatingType(mis.typeA);
addInvalidatingType(mis.typeB);
recordInvalidationError(mis.typeA, mis.src);
recordInvalidationError(mis.typeB, mis.src);
}
StaticScope<T> scope = typeSystem.getRootScope();
NodeTraversal.traverse(compiler, externs, new FindExternProperties());
NodeTraversal.traverse(compiler, root, new FindRenameableProperties());
renameProperties();
}
private void recordInvalidationError(JSType t, JSError error) {
if (!t.isObject()) {
return;
}
if (t.isUnionType()) {
for (JSType alt : t.toMaybeUnionType().getAlternates()) {
recordInvalidationError(alt, error);
}
return;
}
if (invalidationMap != null) {
invalidationMap.put(t, error);
}
}
/**
* Invalidates the given type, so that no properties on it will be renamed.
*/
private void addInvalidatingType(JSType type) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
addInvalidatingType(alt);
}
} else if (type.isEnumElementType()) {
addInvalidatingType(type.toMaybeEnumElementType().getPrimitiveType());
} else {
typeSystem.addInvalidatingType(type);
ObjectType objType = ObjectType.cast(type);
if (objType != null && objType.getImplicitPrototype() != null) {
typeSystem.addInvalidatingType(objType.getImplicitPrototype());
}
}
}
/** Returns the property for the given name, creating it if necessary. */
protected Property getProperty(String name) {
if (!properties.containsKey(name)) {
properties.put(name, new Property(name));
}
return properties.get(name);
}
/** Public for testing. */
T getTypeWithProperty(String field, T type) {
return typeSystem.getTypeWithProperty(field, type);
}
/** Tracks the current type system scope while traversing. */
private abstract class AbstractScopingCallback implements ScopedCallback {
protected final Stack<StaticScope<T>> scopes =
new Stack<StaticScope<T>>();
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
scopes.push(typeSystem.getRootScope());
} else {
scopes.push(typeSystem.getFunctionScope
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(t.getScopeRoot()));
}
}
@Override
public void exitScope(NodeTraversal t) {
scopes.pop();
}
/** Returns the current scope at this point in the file. */
protected StaticScope<T> getScope() {
return scopes.peek();
}
}
/**
* Finds all properties defined in the externs file and sets them as
* ineligible for renaming from the type on which they are defined.
*/
private class FindExternProperties extends AbstractScopingCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(johnlenz): Support object-literal property definitions.
if (n.isGetProp()) {
String field = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), field);
Property prop = getProperty(field);
if (typeSystem.isInvalidatingType(type)) {
prop.invalidate();
} else {
prop.addTypeToSkip(type);
// If this is a prototype property, then we want to skip assignments
// to the instance type as well. These assignments are not usually
// seen in the extern code itself, so we must handle them here.
if ((type = typeSystem.getInstanceFromPrototype(type)) != null) {
prop.getTypes().add(type);
prop.typesToSkip.add(type);
}
}
}
}
}
/**
* Traverses the tree, building a map from field names to Nodes for all
* fields that can be renamed.
*/
private class FindRenameableProperties extends AbstractScopingCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
handleGetProp(t, n);
} else if (n.isObjectLit()) {
handleObjectLit(t, n);
}
}
/**
* Processes a GETPROP node.
*/
private void handleGetProp(NodeTraversal t, Node n) {
String name = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(n.getLastChild(),
processProperty(t, prop, type, null))) {
if (propertiesToErrorFor.containsKey(name)) {
String suggestion = "";
if (type instanceof JSType) {
JSType jsType = (JSType) type;
String qName = n.getFirstChild().getQualifiedName();
if (jsType.isAllType() || jsType.isUnknownType()) {
if (n.getFirstChild().isThis()) {
suggestion = "The \"this\" object is unknown in the function,"+
"consider using @this";
} else {
suggestion = "Consider casting " +
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> qName +
" if you know it's type.";
}
} else {
StringBuilder sb = new StringBuilder();
printErrorLocations(sb, jsType);
if (sb.length() != 0) {
suggestion = "Consider fixing errors for the following types: ";
suggestion += sb.toString();
}
}
}
compiler.report(JSError.make(
t.getSourceName(), n, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()),
n.toString(), suggestion));
}
}
}
/**
* Processes a OBJECTLIT node.
*/
private void handleObjectLit(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
while (child != null) {
// Maybe STRING, GET, SET
// We should never see a mix of numbers and strings.
String name = child.getString();
T type = typeSystem.getType(getScope(), n, name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(child,
processProperty(t, prop, type, null))) {
// TODO(user): It doesn't look like the user can do much in this
// case right now.
if (propertiesToErrorFor.containsKey(name)) {
compiler.report(JSError.make(
t.getSourceName(), child, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString(), ""));
}
}
child = child.getNext();
}
}
private void printErrorLocations(StringBuilder sb, JSType t) {
if (!t.isObject() || t.isAllType() || t.isUnionType()) {
return;
}
if (t.isUnionType()) {
for (JSType alt : t.toMaybeUnionType().getAlternates()) {
printErrorLocations(sb, alt);
}
return;
}
for (JSError error : invalidationMap.get(t)) {
if(sb.length() != 0) {
sb.append(", ");
}
sb.append(t.toString());
sb.append(" at ");
sb.append(error.sourceName);
sb.append(":");
sb.append(error.lineNumber);
}
}
/**
* Processes a property, adding it to the list of properties to rename.
* @return a representative type for the property reference, which will be
* the highest type on the prototype chain of the provided type. In the
* case of a union type, it will be the highest type on the prototype
* chain of one of the members of the union.
*/
private T processProperty(
NodeTraversal t, Property prop, T type, T relatedType) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Check for usage of 'with'.
*
*/
class ControlStructureCheck implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
static final DiagnosticType USE_OF_WITH = DiagnosticType.warning(
"JSC_USE_OF_WITH",
"The use of the 'with' structure should be avoided.");
ControlStructureCheck(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
check(root);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
check(scriptRoot);
}
/**
* Reports errors for any invalid use of control structures.
*
* @param node Current node to check.
*/
private void check(Node node) {
switch (node.getType()) {
case Token.WITH:
JSDocInfo info = node.getJSDocInfo();
boolean allowWith =
info != null && info.getSuppressions().contains("with");
if (!allowWith) {
report(node, USE_OF_WITH);
}
break;
}
for (Node bChild = node.getFirstChild(); bChild != null;) {
Node next = bChild.getNext();
check(bChild);
bChild = next;
}
}
private void report(Node n, DiagnosticType error) {
compiler.report(JSError.make(n.getSourceFileName(), n, error));
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>disabled(
"JSC_DEPRECATED_PROP_REASON",
"Property {0} of type {1} has been deprecated: {2}");
static final DiagnosticType DEPRECATED_CLASS = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS",
"Class {0} has been deprecated.");
static final DiagnosticType DEPRECATED_CLASS_REASON = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS_REASON",
"Class {0} has been deprecated: {1}");
static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_GLOBAL_ACCESS",
"Access to private variable {0} not allowed outside file {1}.");
static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_PROPERTY_ACCESS",
"Access to private property {0} of {1} not allowed here.");
static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PROTECTED_PROPERTY_ACCESS",
"Access to protected property {0} of {1} not allowed here.");
static final DiagnosticType PRIVATE_OVERRIDE =
DiagnosticType.disabled(
"JSC_PRIVATE_OVERRIDE",
"Overriding private property of {0}.");
static final DiagnosticType VISIBILITY_MISMATCH =
DiagnosticType.disabled(
"JSC_VISIBILITY_MISMATCH",
"Overriding {0} property of {1} with {2} property.");
static final DiagnosticType CONST_PROPERTY_REASSIGNED_VALUE =
DiagnosticType.warning(
"JSC_CONSTANT_PROPERTY_REASSIGNED_VALUE",
"constant property {0} assigned a value more than once");
static final DiagnosticType CONST_PROPERTY_DELETED =
DiagnosticType.warning(
"JSC_CONSTANT_PROPERTY_DELETED",
"constant property {0} cannot be deleted");
private final AbstractCompiler compiler;
private final TypeValidator validator;
// State about the current traversal.
private int deprecatedDepth = 0;
private int methodDepth = 0;
private JSType currentClass = null;
private final Multimap<String, String> initializedConstantProperties;
CheckAccessControls(AbstractCompiler compiler) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.initializedConstantProperties = HashMultimap.create();
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
@Override
public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>n, parent)) {
deprecatedDepth++;
}
if (methodDepth == 0) {
currentClass = getClassOfMethod(n, parent);
}
methodDepth++;
}
}
@Override
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth--;
}
methodDepth--;
if (methodDepth == 0) {
currentClass = null;
}
}
}
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.isAssign()) {
Node lValue = parent.getFirstChild();
if (NodeUtil.isGet(lValue)) {
// We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isNominalConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return (lValueType.toMaybeFunctionType()).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.isName()) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isNominalConstructor()) {
return (type.toMaybeFunctionType()).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((ObjectType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
checkConstantProperty(t, n);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.isFunction() || parent.isVar() ||
parent.isNew()) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.isNew()) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJS
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>TypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE) {
StaticSourceFile varSrc = var.getSourceFile();
StaticSourceFile refSrc = name.getStaticSourceFile();
if (varSrc != null &&
refSrc != null &&
!varSrc.getName().equals(refSrc.getName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), varSrc.getName()));
}
}
}
}
}
/**
* Determines whether the given property with @const tag got reassigned
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
boolean isDelete = parent.getType() == Token.DELPROP;
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& (parent.getType() != Token.INC) && (parent.getType() != Token.DEC)
&& !isDelete) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName);
// Check whether constant properties are reassigned
if (isConstant) {
if (isDelete) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName));
return;
}
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if (initializedConstantProperties.containsEntry(
oType.getReferenceName(), propertyName)) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE,
propertyName));
break;
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>ridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always allowed in the same file.
return;
} else if (visibility == Visibility.PRIVATE &&
(currentClass == null || ownerType.differsFrom(currentClass))) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
// private access is not allowed outside the file from a different
// enclosing class.
compiler.report(
t.makeError(getprop,
BAD_PRIVATE_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
} else if (visibility == Visibility.PROTECTED) {
// There are 3 types of legal accesses of a protected property:
// 1) Accesses in the same file
// 2) Overriding the property in a subclass
// 3) Accessing the property from inside a subclass
// The first two have already been checked for.
if (currentClass == null || !currentClass.isSubtype(ownerType)) {
compiler.report(
t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
}
}
}
}
}
/**
* Whether the given access of a private constructor is legal.
*
* For example,
* new PrivateCtor_(); // not legal
* PrivateCtor_.newInstance(); // legal
* x instanceof PrivateCtor_ // legal
*
* This is a weird special case, because our visibility system is inherited
* from Java, and JavaScript has no distinction between classes and
* constructors like Java does.
*
* We may want to revisit this if we decide to make the restrictions tighter.
*/
private static boolean isValidPrivateConstructorAccess(Node parent) {
return parent.getType() != Token.NEW;
}
/**
* Determines whether a deprecation warning should be emitted.
* @param t The current traversal.
* @param n The node which we are checking.
* @param parent The parent of the node which we are checking.
*/
private boolean shouldEmitDeprecationWarning(
NodeTraversal t, Node n, Node parent) {
// In the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.isCall() && parent.getFirstChild() == n) ||
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> n.isNew())) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.isGetProp() && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently ok to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.isAssign() &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n, Node parent) {
if (n.isFunction()) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecationInfo(implicitProto);
}
}
return null;
}
/**
* Returns if a property is declared constant.
*/
private static boolean isPropertyDeclaredConstant(
ObjectType objectType, String prop) {
for (;
// Only objects with reference names can have constant properties.
objectType != null && objectType.hasReferenceName();
objectType = objectType.getImplicitPrototype()) {
JSDocInfo docInfo = object
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> Types for each optional parameter. The builder will make them
* undefineable.
* @return False if this is called after var args are added.
*/
public boolean addOptionalParams(JSType ...types) {
if (hasVarArgs()) {
return false;
}
for (JSType type : types) {
newParameter(registry.createOptionalType(type)).setOptionalArg(true);
}
return true;
}
/**
* Add variable arguments to the end of the parameter list.
* @return False if this is called after var args are added.
*/
public boolean addVarArgs(JSType type) {
if (hasVarArgs()) {
return false;
}
// There are two types of variable argument functions:
// 1) Programmer-defined var args
// 2) Native bottom types that can accept any argument.
// For the first one, "undefined" is a valid value for all arguments.
// For the second, we do not want to cast it up to undefined.
if (!type.isEmptyType()) {
type = registry.createOptionalType(type);
}
newParameter(type).setVarArgs(true);
return true;
}
/**
* Copies the parameter specification from the given node.
*/
public Node newParameterFromNode(Node n) {
Node newParam = newParameter(n.getJSType());
newParam.setVarArgs(n.isVarArgs());
newParam.setOptionalArg(n.isOptionalArg());
return newParam;
}
// Add a parameter to the list with the given type.
private Node newParameter(JSType type) {
Node paramNode = Node.newString(Token.NAME, "");
paramNode.setJSType(type);
root.addChildToBack(paramNode);
return paramNode;
}
public Node build() {
return root;
}
private boolean hasOptionalOrVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null &&
(lastChild.isOptionalArg() || lastChild.isVarArgs());
}
public boolean hasVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null && lastChild.isVarArgs();
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> as a marker
* for a variable argument list function. A VarArgs parameter must be the
* last parameter in a function declaration.
*
* @param parameter The parameter's node.
* @return {@code true} if the parameter should be treated as a variable
* length parameter.
*/
public boolean isVarArgsParameter(Node parameter);
/**
* Checks whether a global variable or function name should be treated as
* exported, or externally referenceable.
*
* @param name A global variable or function name.
* @param local {@code true} if the name is a local variable.
* @return {@code true} if the name should be considered exported.
*/
public boolean isExported(String name, boolean local);
/**
* Should be isExported(name, true) || isExported(name, false);
*/
public boolean isExported(String name);
/**
* Checks whether a name should be considered private. Private global
* variables and functions can only be referenced within the source file in
* which they are declared. Private properties and methods should only be
* accessed by the class that defines them.
*
* @param name The name of a global variable or function, or a method or
* property.
* @return {@code true} if the name should be considered private.
*/
public boolean isPrivate(String name);
/**
* Checks if the given method defines a subclass relationship,
* and if it does, returns information on that relationship. By default,
* always returns null. Meant to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public SubclassRelationship getClassesDefinedByCall(Node callNode);
/**
* Returns true if passed a string referring to the superclass. The string
* will usually be from the string node at the right of a GETPROP, e.g.
* this.superClass_.
*/
public boolean isSuperClassReference(String propertyName);
/**
* Convenience method for determining provided dependencies amongst different
* js scripts.
*/
public String extractClassNameIfProvide(Node node, Node parent);
/**
* Convenience method for determining required dependencies amongst different
* js scripts.
*/
public String extractClassNameIfRequire(Node node, Node parent);
/**
* Function name used when exporting properties.
* Signature: fn(object, publicName, symbol).
* @return function name.
*/
public String getExportPropertyFunction();
/**
* Function name used when exporting symbols.
* Signature: fn(publicPath, object).
* @return function name.
*/
public String getExportSymbolFunction();
/**
* Checks if the given CALL node is forward-declaring any types,
* and returns the name of the types if it is.
*/
public List<String> identifyTypeDeclarationCall(Node n);
/**
* In many JS libraries, the function that produces inheritance also
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> * adds properties to the superclass and/or subclass.
*/
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type);
/**
* Function name for abstract methods. An abstract method can be assigned to
* an interface method instead of an function expression in order to avoid
* linter warnings produced by assigning a function without a return value
* where a return value is expected.
* @return function name.
*/
public String getAbstractMethodName();
/**
* Checks if the given method defines a singleton getter, and if it does,
* returns the name of the class with the singleton getter. By default, always
* returns null. Meant to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public String getSingletonGetterClassName(Node callNode);
/**
* In many JS libraries, the function that adds a singleton getter to a class
* adds properties to the class.
*/
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType);
public DelegateRelationship getDelegateRelationship(Node callNode);
/**
* In many JS libraries, the function that creates a delegate relationship
* also adds properties to the delegator and delegate base.
*/
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate);
/**
* @return the name of the delegate superclass.
*/
public String getDelegateSuperclassName();
/**
* Checks for function calls that set the calling conventions on delegate
* methods.
*/
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions);
/**
* Defines the delegate proxy prototype properties. Their types depend on
* properties of the delegate base methods.
*
* @param delegateProxyPrototypes List of delegate proxy prototypes.
*/
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, Scope scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions);
/**
* Gets the name of the global object.
*/
public String getGlobalObject();
/**
* A Bind instance or null.
*/
public Bind describeFunctionBind(Node n);
public static class Bind {
// The target of the bind action
final Node target;
// The node representing the "this" value, maybe null
final Node thisValue;
// The head of a Node list representing the parameters
final Node parameters;
public Bind(Node target, Node thisValue, Node parameters) {
this.target = target;
this.thisValue = thisValue;
this.parameters = parameters;
}
}
/**
* Whether this CALL function is testing for the existence of a property.
*/
public boolean isPropertyTestFunction(Node call);
/**
* Checks if the given method performs a object literal cast
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Lineno, endCharno, true);
}
return typeNode;
}
/**
* Looks for a parameter type expression at the current token and if found,
* returns it. Note that this method consumes input.
*
* @param token The current token.
* @param lineno The line of the type expression.
* @param startCharno The starting character position of the type expression.
* @param matchingLC Whether the type expression starts with a "{".
* @param onlyParseSimpleNames If true, only simple type names are parsed
* (via a call to parseTypeNameAnnotation instead of
* parseTypeExpressionAnnotation).
* @return The type expression found or null if none.
*/
private Node parseAndRecordTypeNode(JsDocToken token, int lineno,
int startCharno,
boolean matchingLC,
boolean onlyParseSimpleNames) {
Node typeNode = null;
if (onlyParseSimpleNames) {
typeNode = parseTypeNameAnnotation(token);
} else {
typeNode = parseTypeExpressionAnnotation(token);
}
if (typeNode != null && !matchingLC) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
if (typeNode != null) {
int endLineno = stream.getLineno();
int endCharno = stream.getCharno();
jsdocBuilder.markTypeNode(
typeNode, lineno, startCharno, endLineno, endCharno, matchingLC);
}
return typeNode;
}
/**
* Converts a JSDoc token to its string representation.
*/
private String toString(JsDocToken token) {
switch (token) {
case ANNOTATION:
return "@" + stream.getString();
case BANG:
return "!";
case COMMA:
return ",";
case COLON:
return ":";
case GT:
return ">";
case LB:
return "[";
case LC:
return "{";
case LP:
return "(";
case LT:
return ".<";
case QMARK:
return "?";
case PIPE:
return "|";
case RB:
return "]";
case RC:
return "}";
case RP:
return ")";
case STAR:
return "*";
case ELLIPSIS:
return "...";
case EQUALS:
return "=";
case STRING:
return stream.getString();
default:
throw new IllegalStateException(token.toString());
}
}
/**
* Constructs a new {@code JSTypeExpression}.
* @param n A node. May be null.
*/
private JSTypeExpression createJSTypeExpression(Node n) {
return n == null ? null :
new JSTypeExpression(n, getSourceName());
}
/**
* Tuple for returning both the string extracted and the
* new token following a call to any of the extract*Block
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(fieldType);
skipEOLs();
if (!match(JsDocToken.COMMA)) {
break;
}
// Move to the comma token.
next();
// Move to the token passed the comma.
skipEOLs();
token = next();
} while (true);
return fieldTypeList;
}
/**
* FieldType := FieldName | FieldName ':' TypeExpression
*/
private Node parseFieldType(JsDocToken token) {
Node fieldName = parseFieldName(token);
if (fieldName == null) {
return null;
}
skipEOLs();
if (!match(JsDocToken.COLON)) {
return fieldName;
}
// Move to the colon.
next();
// Move to the token after the colon and parse
// the type expression.
skipEOLs();
Node typeExpression = parseTypeExpression(next());
if (typeExpression == null) {
return null;
}
Node fieldType = newNode(Token.COLON);
fieldType.addChildToBack(fieldName);
fieldType.addChildToBack(typeExpression);
return fieldType;
}
/**
* FieldName := NameExpression | StringLiteral | NumberLiteral |
* ReservedIdentifier
*/
private Node parseFieldName(JsDocToken token) {
switch (token) {
case STRING:
String string = stream.getString();
return newStringNode(string);
default:
return null;
}
}
private Node wrapNode(int type, Node n) {
return n == null ? null :
new Node(type, n, stream.getLineno(),
stream.getCharno()).clonePropsFrom(templateNode);
}
private Node newNode(int type) {
return new Node(type, stream.getLineno(),
stream.getCharno()).clonePropsFrom(templateNode);
}
private Node newStringNode(String s) {
return newStringNode(s, stream.getLineno(), stream.getCharno());
}
private Node newStringNode(String s, int lineno, int charno) {
Node n = Node.newString(s, lineno, charno).clonePropsFrom(templateNode);
n.setLength(s.length());
return n;
}
// This is similar to IRFactory.createTemplateNode to share common props
// e.g., source-name, between all nodes.
private Node createTemplateNode() {
// The Node type choice is arbitrary.
Node templateNode = new Node(Token.SCRIPT);
templateNode.setStaticSourceFile(
this.associatedNode != null ?
this.associatedNode.getStaticSourceFile() :
null);
return templateNode;
}
private Node reportTypeSyntaxWarning(String warning) {
parser.addTypeWarning(warning, stream.getLineno(), stream.getCharno());
return null;
}
private Node reportGenericTypeSyntaxWarning() {
return reportTypeSyntaxWarning("msg.js
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> list of neighboring nodes.
*/
public abstract List<GraphNode<N, E>> getNeighborNodes(N value);
public abstract Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value);
/**
* Retrieves an edge from the graph.
*
* @param n1 Node one.
* @param n2 Node two.
* @return The list of edges between those two values in the graph.
*/
public abstract List<GraphEdge<N, E>> getEdges(N n1, N n2);
/**
* Retrieves any edge from the graph.
*
* @param n1 Node one.
* @param n2 Node two.
* @return The first edges between those two values in the graph. null if
* there are none.
*/
public abstract GraphEdge<N, E> getFirstEdge(N n1, N n2);
/**
* Checks whether the node exists in the graph ({@link #createNode(Object)}
* has been called with that value).
*
* @param n Node.
* @return <code>true</code> if it exist.
*/
public final boolean hasNode(N n) {
return getNode(n) != null;
}
/**
* Checks whether two nodes in the graph are connected.
*
* @param n1 Node 1.
* @param n2 Node 2.
* @return <code>true</code> if the two nodes are connected.
*/
public abstract boolean isConnected(N n1, N n2);
/**
* Checks whether two nodes in the graph are connected by the given
* edge type.
*
* @param n1 Node 1.
* @param e The edge type.
* @param n2 Node 2.
*/
public abstract boolean isConnected(N n1, E e, N n2);
/**
* Gets the node of the specified type, or throws an
* IllegalArgumentException.
*/
@SuppressWarnings("unchecked")
<T extends GraphNode<N, E>> T getNodeOrFail(N val) {
T node = (T) getNode(val);
if (node == null) {
throw new IllegalArgumentException(val + " does not exist in graph");
}
return node;
}
@Override
public final void clearNodeAnnotations() {
for (GraphNode<N, E> n : getNodes()) {
n.setAnnotation(null);
}
}
/** Makes each edge's annotation null. */
public final void clearEdgeAnnotations() {
for (GraphEdge<N, E> e : getEdges()) {
e.setAnnotation(null);
}
}
/**
* Pushes nodes' annotation values. Restored with
* {@link #popNodeAnnotations()}. Nodes' annotation values are cleared.
*/
public final void pushNodeAnnotations() {
if (nodeAnnotationStack == null
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>) {
nodeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(nodeAnnotationStack, getNodes());
}
/**
* Restores nodes' annotation values to state before last
* {@link #pushNodeAnnotations()}.
*/
public final void popNodeAnnotations() {
Preconditions.checkNotNull(nodeAnnotationStack,
"Popping node annotations without pushing.");
popAnnotations(nodeAnnotationStack);
}
/**
* Pushes edges' annotation values. Restored with
* {@link #popEdgeAnnotations()}. Edges' annotation values are cleared.
*/
public final void pushEdgeAnnotations() {
if (edgeAnnotationStack == null) {
edgeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(edgeAnnotationStack, getEdges());
}
/**
* Restores edges' annotation values to state before last
* {@link #pushEdgeAnnotations()}.
*/
public final void popEdgeAnnotations() {
Preconditions.checkNotNull(edgeAnnotationStack,
"Popping edge annotations without pushing.");
popAnnotations(edgeAnnotationStack);
}
/**
* A generic edge.
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public interface GraphEdge<N, E> extends Annotatable {
/**
* Retrieves the edge's value.
*
* @return The value.
*/
E getValue();
GraphNode<N, E> getNodeA();
GraphNode<N, E> getNodeB();
}
/**
* A simple implementation of SubGraph that calculates adjacency by iterating
* over a node's neighbors.
*/
class SimpleSubGraph<N, E> implements SubGraph<N, E> {
private Graph<N, E> graph;
private List<GraphNode<N, E>> nodes = Lists.newArrayList();
SimpleSubGraph(Graph<N, E> graph) {
this.graph = graph;
}
@Override
public boolean isIndependentOf(N value) {
GraphNode<N, E> node = graph.getNode(value);
for (GraphNode<N, E> n : nodes) {
if (graph.getNeighborNodes(n.getValue()).contains(node)) {
return false;
}
}
return true;
}
@Override
public void addNode(N value) {
nodes.add(graph.getNodeOrFail(value));
}
}
/**
* Pushes a new list on stack and stores nodes annotations in the new list.
* Clears objects' annotations as well.
*/
private static void pushAnnotations(
Deque<GraphAnnotationState> stack,
Collection<? extends Annotatable> haveAnnotations) {
stack.push(new GraphAnnotationState(haveAnnotations.size()));
for (Annotatable h : haveAnnotations) {
stack.peek().add(new AnnotationState(h, h.getAnnotation()));
h.set
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.GraphReachability;
import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
/**
* Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user
* about unreachable code.
*
*/
class CheckUnreachableCode implements ScopedCallback {
static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error(
"JSC_UNREACHABLE_CODE", "unreachable code");
private final AbstractCompiler compiler;
private final CheckLevel level;
CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.level = level;
}
@Override
public void enterScope(NodeTraversal t) {
initScope(t.getControlFlowGraph());
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n);
if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) {
// Only report error when there are some line number informations.
// There are synthetic nodes with no line number informations, nodes
// introduce by other passes (although not likely since this pass should
// be executed early) or some rhino bug.
if (n.getLineno() != -1 &&
// Allow spurious semi-colons and spurious breaks.
n.getType() != Token.EMPTY && n.getType() != Token.BREAK) {
compiler.report(t.makeError(n, level, UNREACHABLE_CODE));
// From now on
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>, we are going to assume the user fixed the error and not
// give more warning related to code section reachable from this node.
new GraphReachability<Node, ControlFlowGraph.Branch>(
t.getControlFlowGraph()).recompute(n);
// Saves time by not traversing children.
return false;
}
}
return true;
}
private void initScope(ControlFlowGraph<Node> controlFlowGraph) {
new GraphReachability<Node, ControlFlowGraph.Branch>(
controlFlowGraph, new ReachablePredicate()).compute(
controlFlowGraph.getEntry().getValue());
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
private final class ReachablePredicate implements
Predicate<EdgeTuple<Node, ControlFlowGraph.Branch>> {
@Override
public boolean apply(EdgeTuple<Node, Branch> input) {
Branch branch = input.edge;
if (!branch.isConditional()) {
return true;
}
Node predecessor = input.sourceNode;
Node condition = NodeUtil.getConditionExpression(predecessor);
// TODO(user): Handle more complicated expression like true == true,
// etc....
if (condition != null) {
TernaryValue val = NodeUtil.getImpureBooleanValue(condition);
if (val != TernaryValue.UNKNOWN) {
return val.toBoolean(true) == (branch == Branch.ON_TRUE);
}
}
return true;
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> {
Preconditions.checkNotNull(errorManager,
"Expected setErrorManager to be called first");
try {
regenerateDependencyInfoIfNecessary();
return Collections.<String>unmodifiableSet(provides);
} catch (IOException e) {
errorManager.report(CheckLevel.ERROR,
JSError.make(AbstractCompiler.READ_ERROR, getName()));
return ImmutableList.<String>of();
}
}
/**
* Regenerates the provides/requires if we need to do so.
*/
private void regenerateDependencyInfoIfNecessary() throws IOException {
// If the code is NOT a JsAst, then it was not originally JS code.
// Look at the Ast for dependency info.
if (!(ast instanceof JsAst)) {
Preconditions.checkNotNull(compiler,
"Expected setCompiler to be called first");
DepsFinder finder = new DepsFinder();
Node root = getAstRoot(compiler);
if (root == null) {
return;
}
finder.visitTree(getAstRoot(compiler));
// TODO(nicksantos|user): This caching behavior is a bit
// odd, and only works if you assume the exact call flow that
// clients are currently using. In that flow, they call
// getProvides(), then remove the goog.provide calls from the
// AST, and then call getProvides() again.
//
// This won't work for any other call flow, or any sort of incremental
// compilation scheme. The API needs to be fixed so callers aren't
// doing weird things like this, and then we should get rid of the
// multiple-scan strategy.
provides.addAll(finder.provides);
requires.addAll(finder.requires);
} else {
// Otherwise, look at the source code.
if (!generatedDependencyInfoFromSource) {
// Note: it's ok to use getName() instead of
// getPathRelativeToClosureBase() here because we're not using
// this to generate deps files. (We're only using it for
// symbol dependencies.)
DependencyInfo info = (new JsFileParser(errorManager)).parseFile(
getName(), getName(), getCode());
provides.addAll(info.getProvides());
requires.addAll(info.getRequires());
generatedDependencyInfoFromSource = true;
}
}
}
private static class DepsFinder {
private final List<String> provides = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final CodingConvention codingConvention =
new ClosureCodingConvention();
void visitTree(Node n) {
visitSubtree(n, null);
}
void visitSubtree(Node n, Node parent) {
if (n.isCall()) {
String require =
codingConvention.extractClassNameIfRequire(n, parent);
if (require != null) {
requires.add(require);
}
String provide =
codingConvention.extractClassNameIfProvide(n, parent
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>);
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
parent.getType() != Token.EXPR_RESULT &&
parent.getType() != Token.SCRIPT) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
return getSourceFile().getRegion(lineNumber);
}
public String getCode() throws IOException {
return getSourceFile().getCode();
}
/** Returns the module to which the input belongs. */
public JSModule getModule() {
return module;
}
/** Sets the module to which the input belongs. */
public void setModule(JSModule module) {
// An input may only belong to one module.
Preconditions.checkArgument(
module == null || this.module == null || this.module == module);
this.module = module;
}
public boolean isExtern() {
if (ast == null || ast.getSourceFile() == null) {
return false;
}
return ast.getSourceFile().isExtern();
}
void setIsExtern(boolean isExtern) {
if (ast == null || ast.getSourceFile() == null) {
return;
}
ast.getSourceFile().setIsExtern(isExtern);
}
public int getLineOffset(int lineno) {
return ast.getSourceFile().getLineOffset(lineno);
}
/** @return The number of lines in this input. */
public int getNumLines() {
return ast.getSourceFile().getNumLines();
}
@Override
public String toString() {
return getName();
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
*
* @param sourceName The source file name
* @param lineno Line number with source file, or -1 if unknown
* @param charno Column number within line, or -1 for whole line.
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, int lineno, int charno,
CheckLevel level, DiagnosticType type, String... arguments) {
return new JSError(
sourceName, null, lineno, charno, type, level, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, type, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n, CheckLevel level,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, n.getLineno(), n.getCharno(), type, level,
arguments);
}
//
// JSError constructors
//
/**
* Creates a JSError at a CheckLevel for a source file location.
* Private to avoid any entanglement with code outside of the compiler.
*/
private JSError(
String sourceName, @Nullable Node node, int lineno, int charno,
DiagnosticType type, CheckLevel level, String... arguments) {
this.type = type;
this.node = node;
this.description = type.format.format(arguments);
this.lineNumber = lineno;
this.charno = charno;
this.sourceName = sourceName;
this.level = level == null ? type.level : level;
}
/**
* Creates a JSError for a source file location. Private to avoid
* any entanglement with code outside of the compiler.
*/
private JSError(String sourceName, @Nullable Node node,
DiagnosticType type, String... arguments) {
this(sourceName,
node,
(node != null) ? node.getLineno() : -1,
(node != null) ? node.getCharno() : -1,
type, null, arguments);
}
public DiagnosticType getType() {
return type;
}
/**
* Format a message at the
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getImpureBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return getImpureBooleanValue(n.getLastChild());
case Token.NOT:
TernaryValue value = getImpureBooleanValue(n.getLastChild());
return value.not();
case Token.AND: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.or(rhs);
}
case Token.HOOK: {
TernaryValue trueValue = getImpureBooleanValue(
n.getFirstChild().getNext());
TernaryValue falseValue = getImpureBooleanValue(n.getLastChild());
if (trueValue.equals(falseValue)) {
return trueValue;
} else {
return TernaryValue.UNKNOWN;
}
}
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// ignoring side-effects
return TernaryValue.TRUE;
case Token.VOID:
return TernaryValue.FALSE;
default:
return getPureBooleanValue(n);
}
}
/**
* Gets the boolean value of a node that represents a literal. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function
* except it return UNKNOWN for known values with side-effects, use
* getExpressionBooleanValue if you don't care about side-effects.
*/
static TernaryValue getPureBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NOT:
return getPureBooleanValue(n.getLastChild()).not();
case Token.NULL:
case Token.FALSE:
return TernaryValue.FALSE;
case Token.VOID:
if (!mayHaveSideEffects(n.getFirstChild())) {
return TernaryValue.FALSE;
}
break;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>FALSE;
} else if ("Infinity".equals(name)) {
return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.REGEXP:
return TernaryValue.TRUE;
case Token.ARRAYLIT:
case Token.OBJECTLIT:
if (!mayHaveSideEffects(n)) {
return TernaryValue.TRUE;
}
break;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): regex literals as well.
switch (n.getType()) {
case Token.STRING:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
return getStringValue(n.getDouble());
case Token.FALSE:
case Token.TRUE:
case Token.NULL:
return Node.tokenToName(n.getType());
case Token.VOID:
return "undefined";
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? "false" : "true"; // reversed.
}
break;
case Token.ARRAYLIT:
return arrayToString(n);
case Token.OBJECTLIT:
return "[object Object]";
}
return null;
}
static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
/**
* When converting arrays to string using Array.prototype.toString or
* Array.prototype.join, the rules for conversion to String are different
* than converting each element individually. Specifically, "null" and
* "undefined" are converted to an empty string.
* @param n A node that is a member of an Array.
* @return The string representation.
*/
static String getArrayElementStringValue(Node n) {
return (NodeUtil.isNullOrUndefined(n) || n.isEmpty())
? "" : getStringValue(n);
}
static String arrayToString(Node literal) {
Node first = literal.getFirstChild();
StringBuilder result = new StringBuilder();
int nextSlot = 0;
int nextSkipSlot = 0;
for (Node n = first; n != null; n = n
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.getNext()) {
String childValue = getArrayElementStringValue(n);
if (childValue == null) {
return null;
}
if (n != first) {
result.append(',');
}
result.append(childValue);
nextSlot++;
}
return result.toString();
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
if (mayHaveSideEffects(n.getFirstChild())) {
return null;
} else {
return Double.NaN;
}
case Token.NAME:
// Check for known constants
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
}
if (name.equals("NaN")) {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
case Token.NEG:
if (n.getChildCount() == 1 && n.getFirstChild().isName()
&& n.getFirstChild().getString().equals("Infinity")) {
return Double.NEGATIVE_INFINITY;
}
return null;
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? 0.0 : 1.0; // reversed.
}
break;
case Token.STRING:
return getStringNumberValue(n.getString());
case Token.ARRAYLIT:
case Token.OBJECTLIT:
String value = getStringValue(n);
return value != null ? getStringNumberValue(value) : null;
}
return null;
}
static Double getStringNumberValue(String rawJsString) {
if (rawJsString.contains("\u000b")) {
// vertical tab is not always whitespace
return null;
}
String s = trimJsWhiteSpace(rawJsString);
// return ScriptRuntime.toNumber(s);
if (s.length() == 0) {
return 0.0;
}
if (s.length() > 2
&& s.charAt(0) == '0'
&& (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
// Attempt to convert hex numbers.
try {
return Double.valueOf(Integer.parseInt(s.substring(2), 1
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Node parent = n.getParent();
String name = n.getFirstChild().getString();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getString();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
return name != null && name.length() != 0 ? name : null;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
public static String getNearestFunctionName(Node n) {
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> // We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if the operator on this node is symmetric
*/
public static boolean isSymmetricOperation(Node n) {
switch (n.getType()) {
case Token.EQ: // equal
case Token.NE: // not equal
case Token.SHEQ: // exactly equal
case Token.SHNE: // exactly not equal
case Token.MUL: // multiply, unlike add it only works on numbers
// or results NaN if any of the operators is not a number
return true;
}
return false;
}
/**
* Returns true if the operator on this node is relational.
* the returned set does not include the equalities.
*/
public static boolean isRelationalOperation(Node n) {
switch (n.getType()) {
case Token.GT: // equal
case Token.GE: // not equal
case Token.LT: // exactly equal
case Token.LE: // exactly not equal
return true;
}
return false;
}
/**
* Returns the inverse of an operator if it is invertible.
* ex. '>' ==> '<'
*/
public static int getInverseOperator(int type) {
switch (type) {
case Token.GT:
return Token.LT;
case Token.LT:
return Token.GT;
case Token.GE:
return Token.LE;
case Token.LE:
return Token.GE;
}
return Token.ERROR;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did not read
* any captured vairables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.ARRAYLIT:
for (Node child = n.getFirstChild(); child != null;
child = child.getNext
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>()) {
if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Uniary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (!block.isBlock()) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> if (!n.isEmpty()) {
return false;
}
}
return true;
}
static boolean isSimpleOperator(Node n) {
return isSimpleOperatorType(n.getType());
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType(int type) {
switch (type) {
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GETELEM:
case Token.GETPROP:
case Token.GT:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
public static Node newExpr(Node child) {
Node expr = new Node(Token.EXPR_RESULT, child)
.copyInformationFrom(child);
return expr;
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return mayEffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.PARAM_LIST:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(
c.getFirstChild(), checkForNewObjects, compiler)) {
return true;
}
}
return false;
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null) {
return true;
}
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return checkForNewObjects || !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperatorType(n.getType())) {
break;
}
if (isAssignmentOp(n)) {
Node assignTarget = n.getFirstChild();
if (assignTarget.isName()) {
return true;
}
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
if (isGet(assignTarget)) {
// If the object being assigned to is a local object, don't
// consider this a side-effect as it can't be referenced
// elsewhere. Don't do this recursively as the property might
// be an alias of another object, unlike a literal below.
Node current = assignTarget.getFirstChild();
if (evaluatesToLocalValue(current)) {
return false;
}
// A literal value as defined by "isLiteralValue" is guaranteed
// not to be an alias, or any components which are aliases of
// other objects.
// If the root object is a literal don't consider this a
// side-effect.
while (isGet(current)) {
current = current.getFirstChild();
}
return !isLiteralValue(current, true);
} else {
// TODO(johnlenz): remove this code and make this an exception. This
// is here only for legacy reasons, the AST is not valid but
// preserve existing behavior.
return !isLiteralValue(assignTarget, true);
}
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - construtor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
return constructorCallHasSideEffects(callNode, null);
}
static boolean constructorCallHasSideEffects(
Node callNode, AbstractCompiler compiler) {
if (!callNode.isNew()) {
throw new IllegalStateException(
"Expected NEW node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.isName() &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
// A list of built-in object creation or primitive type cast functions that
// can also be called as constructors but lack side-effects.
// TODO(johnlenz): consider adding an extern annotation for this.
private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of(
"Object", "Array", "String", "Number", "Boolean", "RegExp", "Error");
private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECT
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.isCall());
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* @return Whether the new has a local result.
*/
static boolean newHasLocalResult(Node n) {
Preconditions.checkState(n.isNew());
return n.isOnlyModifiesThisCall();
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
* 4 logical-and &&
* 5 bitwise-or |
* 6 bitwise-xor ^
* 7 bitwise-and &
* 8 equality == !=
* 9 relational < <= > >=
* 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
* 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.LSH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.CALL:
case Token.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>GETELEM:
case Token.GETPROP:
// Data values
case Token.ARRAYLIT:
case Token.EMPTY: // TODO(johnlenz): remove this.
case Token.FALSE:
case Token.FUNCTION:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.STRING:
case Token.THIS:
case Token.TRUE:
return 15;
default: throw new Error("Unknown precedence for " +
Node.tokenToName(type) +
" (type " + type + ")");
}
}
/**
* Apply the supplied predicate against the potential
* all possible result of the expression.
*/
static boolean valueCheck(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
return valueCheck(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return valueCheck(n.getFirstChild(), p)
&& valueCheck(n.getLastChild(), p);
case Token.HOOK:
return valueCheck(n.getFirstChild().getNext(), p)
&& valueCheck(n.getLastChild(), p);
default:
return p.apply(n);
}
}
static class NumbericResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isNumericResultHelper(n);
}
}
static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE =
new NumbericResultPredicate();
/**
* Returns true if the result of node evaluation is always a number
*/
static boolean isNumericResult(Node n) {
return valueCheck(n, NUMBERIC_RESULT_PREDICATE);
}
static boolean isNumericResultHelper(Node n) {
switch (n.getType()) {
case Token.ADD:
return !mayBeString(n.getFirstChild())
&& !mayBeString(n.getLastChild());
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.MUL:
case Token.MOD:
case Token.DIV:
case Token.INC:
case Token.DEC:
case Token.POS:
case Token.NEG:
case Token.NUMBER:
return true;
case Token.NAME:
String name = n.getString();
if (name.equals("NaN")) {
return true;
}
if (name.equals("Infinity")) {
return true;
}
return false;
default:
return false;
}
}
static class BooleanResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>) {
return isBooleanResultHelper(n);
}
}
static final BooleanResultPredicate BOOLEAN_RESULT_PREDICATE =
new BooleanResultPredicate();
/**
* @return Whether the result of node evaluation is always a boolean
*/
static boolean isBooleanResult(Node n) {
return valueCheck(n, BOOLEAN_RESULT_PREDICATE);
}
static boolean isBooleanResultHelper(Node n) {
switch (n.getType()) {
// Primitives
case Token.TRUE:
case Token.FALSE:
// Comparisons
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
// Queryies
case Token.IN:
case Token.INSTANCEOF:
// Inversion
case Token.NOT:
// delete operator returns a boolean.
case Token.DELPROP:
return true;
default:
return false;
}
}
static boolean isUndefined(Node n) {
switch (n.getType()) {
case Token.VOID:
return true;
case Token.NAME:
return n.getString().equals("undefined");
}
return false;
}
static boolean isNullOrUndefined(Node n) {
return n.isNull() || isUndefined(n);
}
static class MayBeStringResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return mayBeStringHelper(n);
}
}
static final MayBeStringResultPredicate MAY_BE_STRING_PREDICATE =
new MayBeStringResultPredicate();
/**
* @returns Whether the results is possibly a string.
*/
static boolean mayBeString(Node n) {
return mayBeString(n, true);
}
static boolean mayBeString(Node n, boolean recurse) {
if (recurse) {
return valueCheck(n, MAY_BE_STRING_PREDICATE);
} else {
return mayBeStringHelper(n);
}
}
static boolean mayBeStringHelper(Node n) {
return !isNumericResult(n) && !isBooleanResult(n)
&& !isUndefined(n) && !n.isNull();
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
case Token.BITAND:
return true;
default:
return false;
}
}
/**
* Returns true if the operator is commutative.
* e.g. (a * b) * c = c * (b * a)
* Note 1: "+" is not commutative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
* Note 2: only operations on literals and pure functions are commutative.
*/
static boolean isCommutative(int type) {
switch (type) {
case Token.MUL:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assiment op:" + n);
}
static boolean isExpressionNode(Node n) {
return n.isExprResult();
}
/**
* Determines if the given node contains a function statement or function
* expression.
*/
static boolean containsFunction(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the shallow scope contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
Node start = (n.isFunction()) ? n.getLastChild() : n;
return containsType(start, Token.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>THIS, MATCH_NOT_FUNCTION);
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.isGetProp() || n.isGetElem();
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.isName() && n.getParent().isVar();
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
if (parent.isVar()) {
return n.getFirstChild();
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.isExprResult()
&& n.getFirstChild().isAssign();
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.isExprResult()
&& n.getFirstChild().isCall();
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.isFor()
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (parent.isFunction()) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT_CASE:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT_CASE:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.isScript() || n.isBlock();
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
return isStatementParent(n.getParent());
}
static boolean isStatementParent(Node parent) {
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.isCase() || n.isDefaultCase();
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return n.isName() && !n.getString().isEmpty();
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.isTry() && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Whether the node is a CATCH container BLOCK. */
static boolean isTryCatchNodeContainer(Node n) {
Node parent = n.getParent();
return parent.isTry()
&& parent.getFirstChild().getNext() == n;
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
if (isTryFinallyNode(parent, node)) {
if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) {
// A finally can only be removed if there is a catch.
parent.removeChild(node);
} else {
// Otherwise only its children can be removed.
node.detachChildren();
}
} else if (node.isCatch()) {
// The CATCH can can only be removed if there is a finally clause.
Node tryNode = node.getParent().getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachFromParent();
} else if (isTryCatchNodeContainer(node)) {
// The container node itself can't be removed, but the contained CATCH
// can if there is a 'finally' clause
Node tryNode = node.getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachChildren
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(fn.isFunction());
return fn.getLastChild();
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.isFunction() && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().isScript()
|| n.getParent().getParent().isFunction());
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.isFunction() && !isStatement(n);
}
/**
* Determines if a node is a function expression that has an empty body.
*
* @param node a node
* @return whether the given node is a function expression that is empty
*/
static boolean isEmptyFunctionExpression(Node node) {
return isFunctionExpression(node) && isEmptyBlock(node.getLastChild());
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
Preconditions.checkArgument(function.isFunction());
return isNameReferenced(
function.getLastChild(),
"arguments",
MATCH_NOT_FUNCTION);
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode,
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> String methodName) {
if (callNode.isCall()) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.isString()) {
String propName = last.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
* or
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCallOrApply(Node callNode) {
return isFunctionObjectCall(callNode) || isFunctionObjectApply(callNode);
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
* where x is a NAME node.
*/
static boolean isSimpleFunctionObjectCall(Node callNode) {
if (isFunctionObjectCall(callNode)) {
if (callNode.getFirstChild().getFirstChild().isName()) {
return true;
}
}
return false;
}
/**
* Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isVarOrSimpleAssignLhs(Node n, Node parent) {
return (parent.isAssign() && parent.getFirstChild() == n) ||
parent.isVar();
}
/**
* Determines whether this node is used as an L-value. Notice that sometimes
* names are used as both L-values and R-values.
*
* We treat "var x;" as a pseudo-L-value, which kind of makes sense if you
* treat it as "assignment to 'undefined' at the top of the scope". But if
* we're honest with ourselves, it doesn't make sense, and we only do this
* because it makes
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> sense to treat this as synactically similar to
* "var x = 0;".
*
* @param n The node
* @return True if n is an L-value.
*/
static boolean isLValue(Node n) {
Preconditions.checkArgument(n.isName() || n.isGetProp() ||
n.isGetElem());
Node parent = n.getParent();
return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| (NodeUtil.isForIn(parent) && parent.getFirstChild() == n)
|| parent.isVar()
|| (parent.isFunction() && parent.getFirstChild() == n)
|| parent.isDec()
|| parent.isInc()
|| parent.isParamList()
|| parent.isCatch();
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
switch (node.getType()) {
case Token.STRING:
return parent.isObjectLit();
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected when using the key.
*/
static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) {
if (valueType != null) {
switch (key.getType()) {
case Token.GETTER_DEF:
// GET must always return a function type.
if (valueType.isFunctionType()) {
FunctionType fntype = valueType.toMaybeFunctionType();
valueType = fntype.getReturnType();
} else {
return null;
}
break;
case Token.SETTER_DEF:
if (valueType.isFunctionType()) {
// SET must always return a function type.
FunctionType fntype = valueType.toMaybeFunctionType();
Node param = fntype.getParametersNode().getFirstChild();
// SET function must always have one parameter.
valueType = param.getJSType();
} else {
return null;
}
break;
}
}
return valueType;
}
/**
* Determines whether a node represents an object literal get or set key
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> the operator's token value to convert
* @return the string representation
* @throws Error if the token value is not an operator
*/
static String opToStrNoFail(int operator) {
String res = opToStr(operator);
if (res == null) {
throw new Error("Unknown op " + operator + ": " +
Token.name(operator));
}
return res;
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node,
int type,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = new Node(
Token.VAR,
Node.newString(Token.NAME, nameNode.getString())
.copyInformationFrom(nameNode))
.copyInformationFrom(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.isBlock() ||
addingRoot.isScript());
Preconditions.checkState(addingRoot.getFirstChild() == null ||
!addingRoot.getFirstChild().isScript
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> originalName);
return nameNode;
}
/** Test if all characters in the string are in the Basic Latin (aka ASCII)
* character set - that they have UTF-16 values equal to or below 0x7f.
* This check can find which identifiers with Unicode characters need to be
* escaped in order to allow resulting files to be processed by non-Unicode
* aware UNIX tools and editors.
* *
* See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
* for more on Basic Latin.
*
* @param s The string to be checked for ASCII-goodness.
*
* @return True if all characters in the string are in Basic Latin set.
*/
static boolean isLatin(String s) {
char LARGEST_BASIC_LATIN = 0x7f;
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return false;
}
}
return true;
}
/**
* Determines whether the given name is a valid variable name.
*/
public static boolean isValidSimpleName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
isLatin(name);
}
/**
* Determines whether the given name is a valid qualified name.
*/
// TODO(nicksantos): This should be moved into a "Language" API,
// so that the results are different for es5 and es3.
public static boolean isValidQualifiedName(String name) {
if (name.endsWith(".") || name.startsWith(".")) {
return false;
}
String[] parts = name.split("\\.");
for (String part : parts) {
if (!isValidSimpleName(part)) {
return false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return isValidSimpleName(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
@Override
public void visit(Node n) {
if (n.isName()) {
Node parent = n.getParent();
if (parent != null && parent.isVar()) {
String name = n.getString();
if (!vars.containsKey(
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
public static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
MATCH_NOT_FUNCTION);
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
if (!isExprAssign(n)) {
return false;
}
return isPrototypeProperty(n.getFirstChild().getFirstChild());
}
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
if (lhsString == null) {
return false;
}
int prototypeIdx = lhsString.indexOf(".prototype.");
return prototypeIdx != -1;
}
/**
* @return The class name part of a qualified prototype name.
*/
static Node getPrototypeClassName(Node qName) {
Node cur = qName;
while (cur.isGetProp()) {
if (cur.getLastChild().getString().equals("prototype")) {
return cur.getFirstChild();
} else {
cur = cur.getFirstChild();
}
}
return null;
}
/**
* @return The string property name part of a qualified prototype name.
*/
static String getPrototypePropertyName(Node qName) {
String qNameStr = qName.getQualifiedName();
int prototypeIdx = qNameStr.lastIndexOf(".prototype.");
int memberIndex = prototypeIdx + ".prototype".length() + 1;
return qNameStr.substring(memberIndex);
}
/**
* Create a node for an empty result expression:
* "void 0"
*/
static Node newUndefinedNode(Node srcReferenceNode) {
Node node = new Node(Token.VOID, Node.newNumber(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = Node.newString(Token.NAME, name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.copyInformationFrom(value);
}
Node var = new Node(Token.VAR, nodeName)
.copyInformationFrom(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(String name){
this.name = name;
}
@Override
public boolean apply(Node n) {
return n.isName() && n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
@Override
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.isVar();
}
}
/**
* A predicate for matching anything except function nodes.
*/
private static class MatchNotFunction implements Predicate<Node>{
@Override
public boolean apply(Node n) {
return !n.isFunction();
}
}
static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction();
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
@Override
public boolean apply(Node n) {
Node parent = n.getParent();
return n.isBlock()
|| (!n.isFunction() && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(node)) {
return true;
}
if (!traverseChildrenPred.apply(node)) {
return false;
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
if (has(c, pred, traverseChildrenPred)) {
return true;
}
}
return false;
}
/**
* @return The number of times the the predicate is true for the node
* or any of its children.
*/
static int getCount(
Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) {
int total = 0;
if (pred.apply(n)) {
total++;
}
if (traverseChildrenPred.apply(n)) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
total += getCount(c, pred, traverseChildrenPred);
}
}
return total;
}
/**
* Interface for use with the visit method.
* @see #visit
*/
static interface Visitor {
void visit(Node node);
}
/**
* A pre-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPreOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
vistor.visit(node);
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPreOrder(c, vistor, traverseChildrenPred);
}
}
}
/**
* A post-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPostOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPostOrder(c, vistor, traverseChildrenPred);
}
}
vistor.visit(node);
}
/**
* @return Whether a TRY node has a finally block.
*/
static boolean hasFinally(Node n) {
Preconditions.checkArgument(n.isTry());
return n.getChildCount() == 3;
}
/**
* @return The BLOCK node containing the CATCH node (if any)
* of a TRY.
*/
static Node getCatchBlock(Node n) {
Preconditions.checkArgument(n.isTry());
return n.getFirstChild().getNext();
}
/**
* @return Whether BLOCK (from a TRY node) contains a CATCH.
* @see NodeUtil#getCatchBlock
*/
static boolean hasCatchHandler(Node n) {
Preconditions.checkArgument(n.isBlock());
return n.hasChildren() && n.getFirstChild().isCatch();
}
/**
* @param fnNode The function.
* @return
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> The Node containing the Function parameters.
*/
public static Node getFunctionParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.getFirstChild().getNext();
}
/**
* Returns true if a name node represents a constant variable.
*
* <p>Determining whether a variable is constant has three steps:
* <ol>
* <li>In CodingConventionAnnotator, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* <li>The normalize pass renames any variable with the IS_CONSTANT_NAME
* annotation and that is initialized to a constant value with
* a variable name inlucding $$constant.
* <li>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
String name = node.getString();
if (parent.isGetProp() &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.isFunction());
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.isAssign()) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.isName()) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getSourceFileName();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
*
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> @return The source name property on the node or its ancestors.
*/
public static StaticSourceFile getSourceFile(Node n) {
StaticSourceFile sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getStaticSourceFile();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The InputId property on the node or its ancestors.
*/
public static InputId getInputId(Node n) {
while (n != null && !n.isScript()) {
n = n.getParent();
}
return (n != null && n.isScript()) ? n.getInputId() : null;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = !isGet(callTarget);
Node call = new Node(Token.CALL, callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
|| isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return newHasLocalResult(value)
|| locals.apply(value);
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.DELPROP:
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(function.isFunction());
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return propNode.isString() && "toString".equals(propNode.getString());
}
return false;
}
/** Find the best JSDoc for the given node. */
static JSDocInfo getBestJSDocInfo(Node n) {
JSDocInfo info = n.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>getJSDocInfo();
if (info == null) {
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (parent.isName()) {
return getBestJSDocInfo(parent);
} else if (parent.isAssign()) {
info = parent.getJSDocInfo();
} else if (isObjectLitKey(parent, parent.getParent())) {
info = parent.getJSDocInfo();
} else if (parent.isFunction()) {
info = parent.getJSDocInfo();
} else if (parent.isVar() && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
}
return info;
}
/** Find the l-value that the given r-value is being assigned to. */
static Node getBestLValue(Node n) {
Node parent = n.getParent();
boolean isFunctionDeclaration = isFunctionDeclaration(n);
if (isFunctionDeclaration) {
return n.getFirstChild();
} else if (parent.isName()) {
return parent;
} else if (parent.isAssign()) {
return parent.getFirstChild();
} else if (isObjectLitKey(parent, parent.getParent())) {
return parent;
}
return null;
}
/** Get the owner of the given l-value node. */
static Node getBestLValueOwner(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue, lValue.getParent())) {
return getBestLValue(lValue.getParent());
} else if (isGet(lValue)) {
return lValue.getFirstChild();
}
return null;
}
/** Get the name of the given l-value node. */
static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue, lValue.getParent())) {
Node owner = getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + getObjectLitKeyName(lValue);
}
}
return null;
}
return lValue.getQualifiedName();
}
/**
* @returns false iff the result of the expression is not consumed.
*/
static boolean isExpressionResultUsed(Node expr) {
// TODO(johnlenz): consider sharing some code with trySimpleUnusedResult.
Node parent = expr.getParent();
switch (parent.getType()) {
case Token.EXPR_RESULT:
return false;
case Token.HOOK:
case Token.AND:
case Token.OR:
return (
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Callback
implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
@SuppressWarnings("unused")
private boolean inExterns;
InferJSDocInfo(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (externs != null) {
inExterns = true;
NodeTraversal.traverse(compiler, externs, this);
}
if (root != null) {
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
}
@Override
public void hotSwapScript(Node root, Node originalRoot) {
Preconditions.checkNotNull(root);
Preconditions.checkState(root.isScript());
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo docInfo;
switch (n.getType()) {
// Infer JSDocInfo on types of all type declarations on variables.
case Token.NAME:
if (parent == null) {
return;
}
// Only allow JSDoc on VARs, function declarations, and assigns.
if (parent.getType() != Token.VAR &&
!NodeUtil.isFunctionDeclaration(parent) &&
!(parent.isAssign() &&
n == parent.getFirstChild())) {
return;
}
// There are four places the doc info could live.
// 1) A FUNCTION node.
// /** ... */ function f() { ... }
// 2) An ASSIGN parent.
// /** ... */ x = function () { ... }
// 3) A NAME parent.
// var x, /** ... */ y = function() { ... }
// 4) A VAR gramps.
// /** ... */ var x = function() { ... }
docInfo = n.getJSDocInfo();
if (docInfo == null &&
!(parent.isVar() &&
!parent.hasOneChild())) {
docInfo = parent.getJSDocInfo();
}
// Try to find the type of the NAME.
JSType varType = n.getJSType();
if (varType == null && parent.isFunction()) {
varType = parent.getJSType();
}
// If we have no type to attach JSDocInfo to, then there's nothing
// we can do.
if (varType == null || docInfo == null) {
return;
}
// Dereference the type. If the result is not an object, or already
// has docs attached, then do nothing.
ObjectType objType = dereferenceToObject(varType);
if (objType == null || objType.getJSDocInfo() != null) {
return;
}
attachJSDocInfoToNominalTypeOr
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Shape(objType, docInfo, n.getString());
break;
case Token.GETPROP:
// Infer JSDocInfo on properties.
// There are two ways to write doc comments on a property.
//
// 1)
// /** @deprecated */
// obj.prop = ...
//
// 2)
// /** @deprecated */
// obj.prop;
if (NodeUtil.isExpressionNode(parent) ||
(parent.isAssign() &&
parent.getFirstChild() == n)) {
docInfo = n.getJSDocInfo();
if (docInfo == null) {
docInfo = parent.getJSDocInfo();
}
if (docInfo != null) {
ObjectType lhsType =
dereferenceToObject(n.getFirstChild().getJSType());
if (lhsType != null) {
// Put the JSDoc in the property slot, if there is one.
String propName = n.getLastChild().getString();
if (lhsType.hasOwnProperty(propName)) {
lhsType.setPropertyJSDocInfo(propName, docInfo);
}
// Put the JSDoc in any constructors or function shapes as well.
ObjectType propType =
dereferenceToObject(lhsType.getPropertyType(propName));
if (propType != null) {
attachJSDocInfoToNominalTypeOrShape(
propType, docInfo, n.getQualifiedName());
}
}
}
}
break;
}
}
/**
* Dereferences the given type to an object, or returns null.
*/
private ObjectType dereferenceToObject(JSType type) {
return ObjectType.cast(type == null ? null : type.dereference());
}
/**
* Handle cases #1 and #3 in the class doc.
*/
private void attachJSDocInfoToNominalTypeOrShape(
ObjectType objType, JSDocInfo docInfo, @Nullable String qName) {
if (objType.isConstructor() ||
objType.isEnumType() ||
objType.isInterface()) {
// Named types.
if (objType.hasReferenceName() &&
objType.getReferenceName().equals(qName)) {
objType.setJSDocInfo(docInfo);
if (objType.isConstructor() || objType.isInterface()) {
JSType.toMaybeFunctionType(objType).getInstanceType().setJSDocInfo(
docInfo);
} else if (objType instanceof EnumType) {
((EnumType) objType).getElementsType().setJSDocInfo(docInfo);
}
}
} else if (!objType.isNativeObjectType() &&
objType.isFunctionType()) {
// Structural functions.
objType.setJSDocInfo(docInfo);
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>weakInfo> tweakInfos) {
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
String tweakId = entry.getKey();
TweakInfo tweakInfo = tweakInfos.get(tweakId);
if (tweakInfo == null) {
compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId));
} else {
TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc;
Node value = entry.getValue();
if (!registerFunc.isValidNodeType(value.getType())) {
compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
} else {
tweakInfo.defaultValueNode = value;
}
}
}
}
/**
* Finds all calls to goog.tweak functions and emits warnings/errors if any
* of the calls have issues.
* @return A map of {@link TweakInfo} structures, keyed by tweak ID.
*/
private CollectTweaksResult collectTweaks(Node root) {
CollectTweaks pass = new CollectTweaks();
NodeTraversal.traverse(compiler, root, pass);
Map<String, TweakInfo> tweakInfos = pass.allTweaks;
for (TweakInfo tweakInfo: tweakInfos.values()) {
tweakInfo.emitAllWarnings();
}
return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls);
}
private final static class CollectTweaksResult {
final Map<String, TweakInfo> tweakInfos;
final List<TweakFunctionCall> getOverridesCalls;
CollectTweaksResult(Map<String, TweakInfo> tweakInfos,
List<TweakFunctionCall> getOverridesCalls) {
this.tweakInfos = tweakInfos;
this.getOverridesCalls = getOverridesCalls;
}
}
/**
* Processes all calls to goog.tweak functions.
*/
private final class CollectTweaks extends AbstractPostOrderCallback {
final Map<String, TweakInfo> allTweaks = Maps.newHashMap();
final List<TweakFunctionCall> getOverridesCalls = Lists.newArrayList();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.CALL) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName);
if (tweakFunc == null) {
return;
}
if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) {
getOverridesCalls.add(
new TweakFunctionCall(t.getSourceName(), tweakFunc, n));
return;
}
// Ensure the first parameter (the tweak ID) is a string
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> literal.
Node tweakIdNode = n.getFirstChild().getNext();
if (tweakIdNode.getType() != Token.STRING) {
compiler.report(t.makeError(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR));
return;
}
String tweakId = tweakIdNode.getString();
// Make sure there is a TweakInfo structure for it.
TweakInfo tweakInfo = allTweaks.get(tweakId);
if (tweakInfo == null) {
tweakInfo = new TweakInfo(tweakId);
allTweaks.put(tweakId, tweakInfo);
}
switch (tweakFunc) {
case REGISTER_BOOLEAN:
case REGISTER_NUMBER:
case REGISTER_STRING:
// Ensure the ID contains only valid characters.
if (!ID_MATCHER.matchesAllOf(tweakId)) {
compiler.report(t.makeError(tweakIdNode, INVALID_TWEAK_ID_ERROR));
}
// Ensure tweaks are registered in the global scope.
if (!t.inGlobalScope()) {
compiler.report(
t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId));
break;
}
// Ensure tweaks are registered only once.
if (tweakInfo.isRegistered()) {
compiler.report(
t.makeError(n, TWEAK_MULTIPLY_REGISTERED_ERROR, tweakId));
break;
}
Node tweakDefaultValueNode = tweakIdNode.getNext().getNext();
tweakInfo.addRegisterCall(t.getSourceName(), tweakFunc, n,
tweakDefaultValueNode);
break;
case OVERRIDE_DEFAULT_VALUE:
// Ensure tweaks overrides occur in the global scope.
if (!t.inGlobalScope()) {
compiler.report(
t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId));
break;
}
// Ensure tweak overrides occur before the tweak is registered.
if (tweakInfo.isRegistered()) {
compiler.report(
t.makeError(n, TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR, tweakId));
break;
}
tweakDefaultValueNode = tweakIdNode.getNext();
tweakInfo.addOverrideDefaultValueCall(t.getSourceName(), tweakFunc, n,
tweakDefaultValueNode);
break;
case GET_BOOLEAN:
case GET_NUMBER:
case GET_STRING:
tweakInfo.addGetterCall(t.getSourceName(), tweakFunc, n);
}
}
}
/**
* Holds information about a call to a goog.tweak function.
*/
private static final class TweakFunctionCall {
final String sourceName;
final TweakFunction tweakFunc;
final Node callNode;
final Node valueNode;
TweakFunctionCall(String sourceName, TweakFunction tweakFunc,
Node call
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
/**
* This interface defines how objects capable of creating scopes from the parse
* tree behave.
*
*/
interface ScopeCreator {
/**
* Creates a {@link Scope} object.
*
* @param n the root node (either a FUNCTION node, a SCRIPT node, or a
* synthetic block node whose children are all SCRIPT nodes)
* @param parent the parent Scope object (may be null)
*/
Scope createScope(Node n, Scope parent);
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>isConstructor() ||
info.isInterface();
}
/**
* The scope that we should declare this function in, if it needs
* to be declared in a scope. Notice that TypedScopeCreator takes
* care of most scope-declaring.
*/
private Scope getScopeDeclaredIn() {
int dotIndex = fnName.indexOf(".");
if (dotIndex != -1) {
String rootVarName = fnName.substring(0, dotIndex);
Var rootVar = scope.getVar(rootVarName);
if (rootVar != null) {
return rootVar.getScope();
}
}
return scope;
}
/**
* Check whether a type is resolvable in the future
* If this has a supertype that hasn't been resolved yet, then we can assume
* this type will be ok once the super type resolves.
* @param objectType
* @return true if objectType is resolvable in the future
*/
private static boolean hasMoreTagsToResolve(ObjectType objectType) {
Preconditions.checkArgument(objectType.isUnknownType());
if (objectType.getImplicitPrototype() != null) {
// constructor extends class
if (objectType.getImplicitPrototype().isResolved()) {
return false;
} else {
return true;
}
} else {
// interface extends interfaces
FunctionType ctor = objectType.getConstructor();
if (ctor != null) {
for (ObjectType interfaceType : ctor.getExtendedInterfaces()) {
if (!interfaceType.isResolved()) {
return true;
}
}
}
return false;
}
}
/** Holds data dynamically inferred about functions. */
static interface FunctionContents {
/** Returns the source node of this function. May be null. */
Node getSourceNode();
/** Returns if the function may be in externs. */
boolean mayBeFromExterns();
/** Returns if a return of a real value (not undefined) appears. */
boolean mayHaveNonEmptyReturns();
/** Gets a list of variables in this scope that are escaped. */
Iterable<String> getEscapedVarNames();
}
static class UnknownFunctionContents implements FunctionContents {
private static UnknownFunctionContents singleton =
new UnknownFunctionContents();
static FunctionContents get() {
return singleton;
}
@Override
public Node getSourceNode() {
return null;
}
@Override
public boolean mayBeFromExterns() {
return true;
}
@Override
public boolean mayHaveNonEmptyReturns() {
return true;
}
@Override
public Iterable<String> getEscapedVarNames() {
return ImmutableList.of();
}
}
static class AstFunctionContents implements FunctionContents {
private final Node n;
private boolean hasNonEmptyReturns = false;
private Set<String> escapedVarNames;
AstFunctionContents(Node n) {
this.n = n;
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> @Override
public Node getSourceNode() {
return n;
}
@Override
public boolean mayBeFromExterns() {
return n.isFromExterns();
}
@Override
public boolean mayHaveNonEmptyReturns() {
return hasNonEmptyReturns;
}
void recordNonEmptyReturn() {
hasNonEmptyReturns = true;
}
@Override
public Iterable<String> getEscapedVarNames() {
return escapedVarNames == null
? ImmutableList.<String>of() : escapedVarNames;
}
void recordEscapedVarName(String name) {
if (escapedVarNames == null) {
escapedVarNames = Sets.newHashSet();
}
escapedVarNames.add(name);
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> Node subclass = null;
Node superclass = callNode.getLastChild();
// There are six possible syntaxes for a class-defining method:
// SubClass.inherits(SuperClass)
// goog.inherits(SubClass, SuperClass)
// goog$inherits(SubClass, SuperClass)
// SubClass.mixin(SuperClass.prototype)
// goog.mixin(SubClass.prototype, SuperClass.prototype)
// goog$mixin(SubClass.prototype, SuperClass.prototype)
boolean isDeprecatedCall = callNode.getChildCount() == 2 &&
callName.isGetProp();
if (isDeprecatedCall) {
// SubClass.inherits(SuperClass)
subclass = callName.getFirstChild();
} else if (callNode.getChildCount() == 3) {
// goog.inherits(SubClass, SuperClass)
subclass = callName.getNext();
} else {
return null;
}
if (type == SubclassType.MIXIN) {
// Only consider mixins that mix two prototypes as related to
// inheritance.
if (!endsWithPrototype(superclass)) {
return null;
}
if (!isDeprecatedCall) {
if (!endsWithPrototype(subclass)) {
return null;
}
// Strip off the prototype from the name.
subclass = subclass.getFirstChild();
}
superclass = superclass.getFirstChild();
}
// bail out if either of the side of the "inherits"
// isn't a real class name. This prevents us from
// doing something weird in cases like:
// goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2)
if (subclass != null &&
subclass.isUnscopedQualifiedName() &&
superclass.isUnscopedQualifiedName()) {
return new SubclassRelationship(type, subclass, superclass);
}
}
return null;
}
/**
* Determines whether the given node is a class-defining name, like
* "inherits" or "mixin."
* @return The type of class-defining name, or null.
*/
private SubclassType typeofClassDefiningName(Node callName) {
// Check if the method name matches one of the class-defining methods.
String methodName = null;
if (callName.isGetProp()) {
methodName = callName.getLastChild().getString();
} else if (callName.isName()) {
String name = callName.getString();
int dollarIndex = name.lastIndexOf('$');
if (dollarIndex != -1) {
methodName = name.substring(dollarIndex + 1);
}
}
if (methodName != null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClass
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Reference(String propertyName) {
return "superClass_".equals(propertyName);
}
/**
* Given a qualified name node, returns whether "prototype" is at the end.
* For example:
* a.b.c => false
* a.b.c.prototype => true
*/
private boolean endsWithPrototype(Node qualifiedName) {
return qualifiedName.isGetProp() &&
qualifiedName.getLastChild().getString().equals("prototype");
}
/**
* Exctracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Exctracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.isGetProp()) {
String qualifiedName = callee.getQualifiedName();
if (functionName.equals(qualifiedName)) {
Node target = callee.getNext();
if (target != null && target.isString()) {
className = target.getString();
}
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.getType() == Token.ARRAYLIT) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.isString()) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return null;
}
@Override
public String getAbstractMethodName() {
return "goog
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>TYPE),
new AssertionFunctionSpec("goog.asserts.assertString",
JSTypeNative.STRING_TYPE),
new AssertionFunctionSpec("goog.asserts.assertFunction",
JSTypeNative.FUNCTION_INSTANCE_TYPE),
new AssertionFunctionSpec("goog.asserts.assertObject",
JSTypeNative.OBJECT_TYPE),
new AssertionFunctionSpec("goog.asserts.assertArray",
JSTypeNative.ARRAY_TYPE),
// TODO(agrieve): It would be better if this could make the first
// parameter the type of the second parameter.
new AssertionFunctionSpec("goog.asserts.assertInstanceof",
JSTypeNative.OBJECT_TYPE)
);
}
@Override
public Bind describeFunctionBind(Node n) {
Bind result = super.describeFunctionBind(n);
if (result != null) {
return result;
}
// It would be nice to be able to identify a fn.bind call
// but that requires knowing the type of "fn".
if (n.getType() != Token.CALL) {
return null;
}
Node callTarget = n.getFirstChild();
String name = callTarget.getQualifiedName();
if (name != null) {
if (name.equals("goog.bind")
|| name.equals("goog$bind")) {
// goog.bind(fn, self, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = safeNext(fn);
Node parameters = safeNext(thisValue);
return new Bind(fn, thisValue, parameters);
}
if (name.equals("goog.partial") || name.equals("goog$partial")) {
// goog.partial(fn, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = null;
Node parameters = safeNext(fn);
return new Bind(fn, thisValue, parameters);
}
}
return null;
}
private Node safeNext(Node n) {
if (n != null) {
return n.getNext();
}
return null;
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public int getIndexOfChild(Node child) {
Node n = first;
int i = 0;
while (n != null) {
if (child == n) {
return i;
}
n = n.next;
i++;
}
return -1;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
}
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = first;
first = child;
if (last == null) {
last = child;
}
}
public void addChildToBack(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> {
default:
value = x.toString();
break;
}
sb.append(value);
sb.append(']');
}
}
if (printType) {
if (jsType != null) {
String jsTypeString = jsType.toString();
if (jsTypeString != null) {
sb.append(" : ");
sb.append(jsTypeString);
}
}
}
}
public String toStringTree() {
return toStringTreeImpl();
}
private String toStringTreeImpl() {
try {
StringBuilder s = new StringBuilder();
appendStringTree(s);
return s.toString();
} catch (IOException e) {
throw new RuntimeException("Should not happen\n" + e);
}
}
public void appendStringTree(Appendable appendable) throws IOException {
toStringTreeHelper(this, 0, appendable);
}
private static void toStringTreeHelper(Node n, int level, Appendable sb)
throws IOException {
for (int i = 0; i != level; ++i) {
sb.append(" ");
}
sb.append(n.toString());
sb.append('\n');
for (Node cursor = n.getFirstChild();
cursor != null;
cursor = cursor.getNext()) {
toStringTreeHelper(cursor, level + 1, sb);
}
}
int type; // type of the node; Token.NAME for example
Node next; // next sibling
private Node first; // first element of a linked list of children
private Node last; // last element of a linked list of children
/**
* Linked list of properties. Since vast majority of nodes would have
* no more then 2 properties, linked list saves memory and provides
* fast lookup. If this does not holds, propListHead can be replaced
* by UintMap.
*/
private PropListItem propListHead;
/**
* COLUMN_BITS represents how many of the lower-order bits of
* sourcePosition are reserved for storing the column number.
* Bits above these store the line number.
* This gives us decent position information for everything except
* files already passed through a minimizer, where lines might
* be longer than 4096 characters.
*/
public static final int COLUMN_BITS = 12;
/**
* MAX_COLUMN_NUMBER represents the maximum column number that can
* be represented. JSCompiler's modifications to Rhino cause all
* tokens located beyond the maximum column to MAX_COLUMN_NUMBER.
*/
public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1;
/**
* COLUMN_MASK stores a value where bits storing the column number
* are set, and bits storing the line are not set. It's handy for
* separating column number from line number.
*/
public static final int COLUMN_MASK = MAX_COLUMN_NUMBER;
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> final class SiblingNodeIterable
implements Iterable<Node>, Iterator<Node> {
private final Node start;
private Node current;
private boolean used;
SiblingNodeIterable(Node start) {
this.start = start;
this.current = start;
this.used = false;
}
@Override
public Iterator<Node> iterator() {
if (!used) {
used = true;
return this;
} else {
// We have already used the current object as an iterator;
// we must create a new SiblingNodeIterable based on this
// iterable's start node.
//
// Since the primary use case for Node.children is in for
// loops, this branch is extremely unlikely.
return (new SiblingNodeIterable(start)).iterator();
}
}
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Node next() {
if (current == null) {
throw new NoSuchElementException();
}
try {
return current;
} finally {
current = current.getNext();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
// ==========================================================================
// Accessors
PropListItem getPropListHeadForTesting() {
return propListHead;
}
public Node getParent() {
return parent;
}
/**
* Gets the ancestor node relative to this.
*
* @param level 0 = this, 1 = the parent, etc.
*/
public Node getAncestor(int level) {
Preconditions.checkArgument(level >= 0);
Node node = this;
while (node != null && level-- > 0) {
node = node.getParent();
}
return node;
}
/**
* Iterates all of the node's ancestors excluding itself.
*/
public AncestorIterable getAncestors() {
return new AncestorIterable(this.getParent());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
@Override
public Iterator<Node> iterator() {
return new Iterator<Node>() {
@Override
public boolean hasNext() {
return cur != null;
}
@Override
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
Node n = cur;
cur = cur.getParent();
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
*
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
*
* @return Whether the node more than one child.
*/
public boolean hasMoreThanOneChild() {
return first != null && first != last;
}
public int getChildCount() {
int c = 0;
for (Node n = first; n != null; n = n.next)
c++;
return c;
}
// Intended for testing and verification only.
public boolean hasChild(Node child) {
for (Node n = first; n != null; n = n.getNext()) {
if (child == n) {
return true;
}
}
return false;
}
/**
* Checks if the subtree under this node is the same as another subtree.
* Returns null if it's equal, or a message describing the differences.
*/
public String checkTreeEquals(Node node2) {
NodeMismatch diff = checkTreeEqualsImpl(node2);
if (diff != null) {
return "Node tree inequality:" +
"\nTree1:\n" + toStringTree() +
"\n\nTree2:\n" + node2.toStringTree() +
"\n\nSubtree1: " + diff.nodeA.toStringTree() +
"\n\nSubtree2: " + diff.nodeB.toStringTree();
}
return null;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeEqualsImpl(Node node2) {
if (!isEquivalentTo(node2, false, false)) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
if (node2 == null) {
throw new IllegalStateException();
}
res = n.checkTreeEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) {
// Do a non-recursive equivalents check.
if (!isEquivalentTo(node2, true, false)) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
public static String tokenToName(int token) {
switch (token) {
case Token.ERROR: return "error";
case Token.RETURN: return "return";
case Token.BITOR: return "bitor";
case Token.BITXOR: return "bitxor";
case Token.BITAND: return "bitand";
case Token.EQ: return "eq";
case Token.NE: return "ne";
case Token.LT: return "lt";
case Token.LE: return "le";
case Token.GT: return "gt";
case Token.GE: return "ge";
case Token.LSH: return "lsh";
case Token.RSH: return "rsh";
case Token.URSH: return "ursh";
case Token.ADD: return "add";
case Token.SUB: return "sub";
case Token.MUL: return "mul";
case Token.DIV: return "div";
case Token.MOD: return "mod";
case Token.BITNOT: return "bitnot";
case Token.NEG: return "neg";
case Token.NEW: return "new";
case Token.DELPROP: return "delprop";
case Token.TYPEOF: return "typeof";
case Token.GETPROP: return "getprop";
case Token.GETELEM: return "getelem";
case Token.CALL: return "call";
case Token.NAME: return "name";
case Token.NUMBER: return "number";
case Token.STRING: return "string";
case Token.NULL: return "null";
case Token.THIS: return "this";
case Token.FALSE: return "false";
case Token.TRUE: return "true";
case Token.SHEQ: return "sheq";
case Token.SHNE: return "shne";
case Token.REGEXP: return "regexp";
case Token.POS: return "pos";
case Token.THROW: return "throw";
case Token.IN: return "in";
case Token.INSTANCEOF: return "instanceof";
case Token.TRY: return "try";
case Token.PARAM_LIST: return "lp";
case Token.COMMA: return "comma";
case Token.ASSIGN: return "assign";
case Token.ASSIGN_BITOR: return "assign_b
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
if (type != node.getType()
|| getChildCount() != node.getChildCount()
|| this.getClass() != node.getClass()) {
return false;
}
if (compareJsType && !JSType.isEquivalent(jsType, node.getJSType())) {
return false;
}
if (type == Token.INC || type == Token.DEC) {
int post1 = this.getIntProp(INCRDECR_PROP);
int post2 = node.getIntProp(INCRDECR_PROP);
if (post1 != post2) {
return false;
}
} else if (type == Token.STRING) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
} else if (type == Token.CALL) {
if (this.getBooleanProp(FREE_CALL) != node.getBooleanProp(FREE_CALL)) {
return false;
}
}
if (recurse) {
Node n, n2;
for (n = first, n2 = node.first;
n != null;
n = n.next, n2 = n2.next) {
if (!n.isEquivalentTo(n2, compareJsType, true)) {
return false;
}
}
}
return true;
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
return getString();
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its children.
*/
public Node cloneNode() {
Node result;
try {
result = (Node) super.clone();
// PropListItem lists are immutable and can be shared so there is no
// need to clone them here.
result.next = null;
result.first = null;
result.last = null;
result.parent = null;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
return result;
}
/**
* @return A detached clone of the Node and all its children.
*/
public
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractShallowCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Insures '@constructor X' has a 'goog.provide("X")' .
*
*/
class CheckProvides implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
private final CheckLevel checkLevel;
private final CodingConvention codingConvention;
static final DiagnosticType MISSING_PROVIDE_WARNING = DiagnosticType.disabled(
"JSC_MISSING_PROVIDE",
"missing goog.provide(''{0}'')");
CheckProvides(AbstractCompiler compiler, CheckLevel checkLevel) {
this.compiler = compiler;
this.checkLevel = checkLevel;
this.codingConvention = compiler.getCodingConvention();
}
@Override
public void process(Node externs, Node root) {
hotSwapScript(root, null);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
CheckProvidesCallback callback =
new CheckProvidesCallback(codingConvention);
new NodeTraversal(compiler, callback).traverse(scriptRoot);
}
private class CheckProvidesCallback extends AbstractShallowCallback {
private final Map<String, Node> provides = Maps.newHashMap();
private final Map<String, Node> ctors = Maps.newHashMap();
private final CodingConvention convention;
CheckProvidesCallback(CodingConvention convention){
this.convention = convention;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
String providedClassName =
codingConvention.extractClassNameIfProvide(n, parent);
if (providedClassName != null) {
provides.put(providedClassName, n);
}
break;
case Token
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.FUNCTION:
visitFunctionNode(n, parent);
break;
case Token.SCRIPT:
visitScriptNode(t, n);
}
}
private void visitFunctionNode(Node n, Node parent) {
Node name = null;
JSDocInfo info = parent.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = parent.getFirstChild();
} else {
// look to the child, maybe it's a named function
info = n.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = n.getFirstChild();
}
}
if (name != null && name.isQualifiedName()) {
String qualifiedName = name.getQualifiedName();
if (!this.convention.isPrivate(qualifiedName)) {
Visibility visibility = info.getVisibility();
if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) {
ctors.put(qualifiedName, name);
}
}
}
}
private void visitScriptNode(NodeTraversal t, Node n) {
for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) {
if (!provides.containsKey(ctorEntry.getKey())) {
compiler.report(
t.makeError(ctorEntry.getValue(), checkLevel,
MISSING_PROVIDE_WARNING, ctorEntry.getKey()));
}
}
provides.clear();
ctors.clear();
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> : number\n" +
"required: E.<number>");
}
public void testEnum20() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2}; var x = []; x[E.A] = 0;");
}
public void testEnum21() throws Exception {
Node n = parseAndTypeCheck(
"/** @enum {string} */ var E = {A : 'a', B : 'b'};\n" +
"/** @param {!E} x\n@return {!E} */ function f(x) { return x; }");
Node nodeX = n.getLastChild().getLastChild().getLastChild().getLastChild();
JSType typeE = nodeX.getJSType();
assertFalse(typeE.isObject());
assertFalse(typeE.isNullable());
}
public void testEnum22() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2};" +
"/** @param {E} x \n* @return {number} */ function f(x) {return x}");
}
public void testEnum23() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2};" +
"/** @param {E} x \n* @return {string} */ function f(x) {return x}",
"inconsistent return type\n" +
"found : E.<number>\n" +
"required: string");
}
public void testEnum24() throws Exception {
testTypes("/**@enum {Object} */ var E = {A: {}};" +
"/** @param {E} x \n* @return {!Object} */ function f(x) {return x}",
"inconsistent return type\n" +
"found : E.<(Object|null)>\n" +
"required: Object");
}
public void testEnum25() throws Exception {
testTypes("/**@enum {!Object} */ var E = {A: {}};" +
"/** @param {E} x \n* @return {!Object} */ function f(x) {return x}");
}
public void testEnum26() throws Exception {
testTypes("var a = {}; /**@enum*/ a.B = {A: 1, B: 2};" +
"/** @param {a.B} x \n* @return {number} */ function f(x) {return x}");
}
public void testEnum27() throws Exception {
// x is unknown
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"function f(x) { return A == x; }");
}
public void testEnum28() throws Exception {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> of g does not match formal parameter\n" +
"found : Error\n" +
"required: Array");
}
public void testBackwardsTypedefUse10() throws Exception {
testTypes(
"/** @param {goog.MyEnum} x */ function g(x) {}" +
"var goog = {};" +
"/** @enum {goog.MyTypedef} */ goog.MyEnum = {FOO: 1};" +
"/** @typedef {number} */ goog.MyTypedef;" +
"g(1);",
"actual parameter 1 of g does not match formal parameter\n" +
"found : number\n" +
"required: goog.MyEnum.<number>");
}
public void testBackwardsConstructor1() throws Exception {
testTypes(
"function f() { (new Foo(true)); }" +
"/** \n * @constructor \n * @param {number} x */" +
"var Foo = function(x) {};",
"actual parameter 1 of Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testBackwardsConstructor2() throws Exception {
testTypes(
"function f() { (new Foo(true)); }" +
"/** \n * @constructor \n * @param {number} x */" +
"var YourFoo = function(x) {};" +
"/** \n * @constructor \n * @param {number} x */" +
"var Foo = YourFoo;",
"actual parameter 1 of Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testMinimalConstructorAnnotation() throws Exception {
testTypes("/** @constructor */function Foo(){}");
}
public void testGoodExtends1() throws Exception {
// A minimal @extends example
testTypes("/** @constructor */function base() {}\n" +
"/** @constructor\n * @extends {base} */function derived() {}\n");
}
public void testGoodExtends2() throws Exception {
testTypes("/** @constructor\n * @extends base */function derived() {}\n" +
"/** @constructor */function base() {}\n");
}
public void testGoodExtends3() throws Exception {
testTypes("/** @constructor\n * @extends {Object} */function base() {}\n" +
"/** @constructor\n * @extends {base} */function derived() {}\n");
}
public void testGoodExtends4() throws Exception {
// Ensure that @extends actually sets the base type of a constructor
// correctly. Because this isn't part of the human-readable Function
// definition, we need to crawl the prototype chain (eww).
Node n = parseAndTypeCheck(
"var goog
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> = {};\n" +
"/** @constructor */goog.Base = function(){};\n" +
"/** @constructor\n" +
" * @extends {goog.Base} */goog.Derived = function(){};\n");
Node subTypeName = n.getLastChild().getLastChild().getFirstChild();
assertEquals("goog.Derived", subTypeName.getQualifiedName());
FunctionType subCtorType =
(FunctionType) subTypeName.getNext().getJSType();
assertEquals("goog.Derived", subCtorType.getInstanceType().toString());
JSType superType = subCtorType.getPrototype().getImplicitPrototype();
assertEquals("goog.Base", superType.toString());
}
public void testGoodExtends5() throws Exception {
// we allow for the extends annotation to be placed first
testTypes("/** @constructor */function base() {}\n" +
"/** @extends {base}\n * @constructor */function derived() {}\n");
}
public void testGoodExtends6() throws Exception {
testFunctionType(
CLOSURE_DEFS +
"/** @constructor */function base() {}\n" +
"/** @return {number} */ " +
" base.prototype.foo = function() { return 1; };\n" +
"/** @extends {base}\n * @constructor */function derived() {}\n" +
"goog.inherits(derived, base);",
"derived.superClass_.foo",
"function (this:base): number");
}
public void testGoodExtends7() throws Exception {
testFunctionType(
"Function.prototype.inherits = function(x) {};" +
"/** @constructor */function base() {}\n" +
"/** @extends {base}\n * @constructor */function derived() {}\n" +
"derived.inherits(base);",
"(new derived).constructor",
"function (new:derived): undefined");
}
public void testGoodExtends8() throws Exception {
testTypes("/** @constructor \n @extends {Base} */ function Sub() {}" +
"/** @return {number} */ function f() { return (new Sub()).foo; }" +
"/** @constructor */ function Base() {}" +
"/** @type {boolean} */ Base.prototype.foo = true;",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testGoodExtends9() throws Exception {
testTypes(
"/** @constructor */ function Super() {}" +
"Super.prototype.foo = function() {};" +
"/** @constructor \n * @extends {Super} */ function Sub() {}" +
"Sub.prototype = new Super();" +
"/** @override */ Sub.prototype.foo = function() {};");
}
public void testGoodExtends10() throws Exception {
testTypes(
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
}
public void testImplicitCast() throws Exception {
testTypes("/** @constructor */ function Element() {};\n" +
"/** @type {string}\n" +
" * @implicitCast */" +
"Element.prototype.innerHTML;",
"(new Element).innerHTML = new Array();", null, false);
}
public void testImplicitCastSubclassAccess() throws Exception {
testTypes("/** @constructor */ function Element() {};\n" +
"/** @type {string}\n" +
" * @implicitCast */" +
"Element.prototype.innerHTML;" +
"/** @constructor \n @extends Element */" +
"function DIVElement() {};",
"(new DIVElement).innerHTML = new Array();",
null, false);
}
public void testImplicitCastNotInExterns() throws Exception {
testTypes("/** @constructor */ function Element() {};\n" +
"/** @type {string}\n" +
" * @implicitCast */" +
"Element.prototype.innerHTML;" +
"(new Element).innerHTML = new Array();",
new String[] {
"Illegal annotation on innerHTML. @implicitCast may only be " +
"used in externs.",
"assignment to property innerHTML of Element\n" +
"found : Array\n" +
"required: string"});
}
public void testNumberNode() throws Exception {
Node n = typeCheck(Node.newNumber(0));
assertEquals(NUMBER_TYPE, n.getJSType());
}
public void testStringNode() throws Exception {
Node n = typeCheck(Node.newString("hello"));
assertEquals(STRING_TYPE, n.getJSType());
}
public void testBooleanNodeTrue() throws Exception {
Node trueNode = typeCheck(new Node(Token.TRUE));
assertEquals(BOOLEAN_TYPE, trueNode.getJSType());
}
public void testBooleanNodeFalse() throws Exception {
Node falseNode = typeCheck(new Node(Token.FALSE));
assertEquals(BOOLEAN_TYPE, falseNode.getJSType());
}
public void testUndefinedNode() throws Exception {
Node p = new Node(Token.ADD);
Node n = Node.newString(Token.NAME, "undefined");
p.addChildToBack(n);
p.addChildToBack(Node.newNumber(5));
typeCheck(p);
assertEquals(VOID_TYPE, n.getJSType());
}
public void testNumberAutoboxing() throws Exception {
testTypes("/** @type Number */var a = 4;",
"initializing variable\n" +
"found : number\n" +
"required: (Number|null)");
}
public void testNumberUnboxing() throws Exception {
testTypes("/** @type number */var a = new Number(4);",
"initializing variable\n" +
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
"actual parameter 1 of ns.ns2.Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testQualifiedNameInference10() throws Exception {
testTypes(
"var ns = {}; " +
"ns.ns2 = {}; " +
"(function() { " +
" /** @interface */ " +
" ns.ns2.Foo = function() {};" +
" /** @constructor \n * @implements {ns.ns2.Foo} */ " +
" function F() {}" +
" (new F());" +
"})();");
}
public void testSheqRefinedScope() throws Exception {
Node n = parseAndTypeCheck(
"/** @constructor */function A() {}\n" +
"/** @constructor \n @extends A */ function B() {}\n" +
"/** @return {number} */\n" +
"B.prototype.p = function() { return 1; }\n" +
"/** @param {A} a\n @param {B} b */\n" +
"function f(a, b) {\n" +
" b.p();\n" +
" if (a === b) {\n" +
" b.p();\n" +
" }\n" +
"}");
Node nodeC = n.getLastChild().getLastChild().getLastChild().getLastChild()
.getLastChild().getLastChild();
JSType typeC = nodeC.getJSType();
assertTrue(typeC.isNumber());
Node nodeB = nodeC.getFirstChild().getFirstChild();
JSType typeB = nodeB.getJSType();
assertEquals("B", typeB.toString());
}
public void testAssignToUntypedVariable() throws Exception {
Node n = parseAndTypeCheck("var z; z = 1;");
Node assign = n.getLastChild().getFirstChild();
Node node = assign.getFirstChild();
assertFalse(node.getJSType().isUnknownType());
assertEquals("number", node.getJSType().toString());
}
public void testAssignToUntypedProperty() throws Exception {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {}\n" +
"Foo.prototype.a = 1;" +
"(new Foo).a;");
Node node = n.getLastChild().getFirstChild();
assertFalse(node.getJSType().isUnknownType());
assertTrue(node.getJSType().isNumber());
}
public void testNew1() throws Exception {
testTypes("new 4", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew2() throws Exception {
testTypes("var Math = {}; new Math()", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> +
"* @param {number} start\n" +
"* @param {number} opt_length\n" +
"* @return {string}\n" +
"*/\n" +
"String.prototype.substr = function(start, opt_length) {};\n";
Node n2 = parseAndTypeCheck(externs + "\"x\".substr(0,1);");
assertEquals(STRING_TYPE, n2.getLastChild().getFirstChild().getJSType());
}
public void testExtendFunction1() throws Exception {
Node n = parseAndTypeCheck("/**@return {number}*/Function.prototype.f = " +
"function() { return 1; };\n" +
"(new Function()).f();");
JSType type = n.getLastChild().getLastChild().getJSType();
assertEquals(NUMBER_TYPE, type);
}
public void testExtendFunction2() throws Exception {
Node n = parseAndTypeCheck("/**@return {number}*/Function.prototype.f = " +
"function() { return 1; };\n" +
"(function() {}).f();");
JSType type = n.getLastChild().getLastChild().getJSType();
assertEquals(NUMBER_TYPE, type);
}
public void testInheritanceCheck1() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};");
}
public void testInheritanceCheck2() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};",
"property foo not defined on any superclass of Sub");
}
public void testInheritanceCheck3() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"Super.prototype.foo = function() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};",
"property foo already defined on superclass Super; " +
"use @override to override it");
}
public void testInheritanceCheck4() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"Super.prototype.foo = function() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};");
}
public void testInheritanceCheck5() throws Exception {
testTypes(
"/** @constructor */function Root() {};" +
"Root.prototype.foo = function() {};" +
"/** @constructor\n @extends {Root} */function Super() {};"
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>/** @desc description */Int.prototype.foo = function() {};" +
"/** @interface \n @extends {Int} */function Int2() {};" +
"/** @constructor\n @implements {Int2} */function Foo() {};",
"property foo on interface Int is not implemented by type Foo");
}
public void testStubConstructorImplementingInterface() throws Exception {
// This does not throw a warning for unimplemented property because Foo is
// just a stub.
testTypes(
// externs
"/** @interface */ function Int() {}\n" +
"/** @desc description */Int.prototype.foo = function() {};" +
"/** @constructor \n @implements {Int} */ var Foo;\n",
"", null, false);
}
public void testObjectLiteral() throws Exception {
Node n = parseAndTypeCheck("var a = {m1: 7, m2: 'hello'}");
Node nameNode = n.getFirstChild().getFirstChild();
Node objectNode = nameNode.getFirstChild();
// node extraction
assertEquals(Token.NAME, nameNode.getType());
assertEquals(Token.OBJECTLIT, objectNode.getType());
// value's type
ObjectType objectType =
(ObjectType) objectNode.getJSType();
assertEquals(NUMBER_TYPE, objectType.getPropertyType("m1"));
assertEquals(STRING_TYPE, objectType.getPropertyType("m2"));
// variable's type
assertEquals(objectType, nameNode.getJSType());
}
public void testObjectLiteralDeclaration1() throws Exception {
testTypes(
"var x = {" +
"/** @type {boolean} */ abc: true," +
"/** @type {number} */ 'def': 0," +
"/** @type {string} */ 3: 'fgh'" +
"};");
}
public void testObjectLiteralDeclaration2() throws Exception {
testTypes(
"var x = {" +
" /** @type {boolean} */ abc: true" +
"};" +
"x.abc = 0;",
"assignment to property abc of x\n" +
"found : number\n" +
"required: boolean");
}
public void testObjectLiteralDeclaration3() throws Exception {
testTypes(
"/** @param {{foo: !Function}} x */ function f(x) {}" +
"f({foo: function() {}});");
}
public void testObjectLiteralDeclaration4() throws Exception {
testClosureTypes(
"var x = {" +
" /** @param {boolean} x */ abc: function(x) {}" +
"};" +
"/**\n" +
" * @param {string} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};",
"assignment to property abc of x\n
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>" +
"found : function (string): undefined\n" +
"required: function (boolean): undefined");
// TODO(user): suppress {duplicate} currently also silence the
// redefining type error in the TypeValidator. May be it needs
// a new suppress name instead?
}
public void testObjectLiteralDeclaration5() throws Exception {
testTypes(
"var x = {" +
" /** @param {boolean} x */ abc: function(x) {}" +
"};" +
"/**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};");
}
public void testObjectLiteralDeclaration6() throws Exception {
testTypes(
"var x = {};" +
"/**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};" +
"x = {" +
" /**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */" +
" abc: function(x) {}" +
"};");
}
public void testObjectLiteralDeclaration7() throws Exception {
testTypes(
"var x = {};" +
"/**\n" +
" * @type {function(boolean): undefined}\n" +
" */ x.abc = function(x) {};" +
"x = {" +
" /**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */" +
" abc: function(x) {}" +
"};");
}
public void testCallDateConstructorAsFunction() throws Exception {
// ECMA-262 15.9.2: When Date is called as a function rather than as a
// constructor, it returns a string.
Node n = parseAndTypeCheck("Date()");
assertEquals(STRING_TYPE, n.getFirstChild().getFirstChild().getJSType());
}
// According to ECMA-262, Error & Array function calls are equivalent to
// constructor calls.
public void testCallErrorConstructorAsFunction() throws Exception {
Node n = parseAndTypeCheck("Error('x')");
assertEquals(ERROR_TYPE,
n.getFirstChild().getFirstChild().getJSType());
}
public void testCallArrayConstructorAsFunction() throws Exception {
Node n = parseAndTypeCheck("Array()");
assertEquals(ARRAY_TYPE,
n.getFirstChild().getFirstChild().getJSType());
}
public void testPropertyTypeOfUnionType() throws Exception {
testTypes("var a = {};" +
"
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
public void testResolutionViaRegistry4() throws Exception {
testTypes("/** @constructor */ u.A = function() {};\n" +
"/**\n* @constructor\n* @extends {u.A}\n*/\nu.A.A = function() {}\n;" +
"/**\n* @constructor\n* @extends {u.A}\n*/\nu.A.B = function() {};\n" +
"var ab = new u.A.B();\n" +
"/** @type {!u.A} */ var a = ab;\n" +
"/** @type {!u.A.A} */ var aa = ab;\n",
"initializing variable\n" +
"found : u.A.B\n" +
"required: u.A.A");
}
public void testResolutionViaRegistry5() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ u.T = function() {}; u.T");
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertTrue(type instanceof FunctionType);
assertEquals("u.T",
((FunctionType) type).getInstanceType().getReferenceName());
}
public void testGatherProperyWithoutAnnotation1() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ var T = function() {};" +
"/** @type {!T} */var t; t.x; t;");
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertTrue(type instanceof ObjectType);
ObjectType objectType = (ObjectType) type;
assertFalse(objectType.hasProperty("x"));
assertEquals(
Lists.newArrayList(objectType),
registry.getTypesWithProperty("x"));
}
public void testGatherProperyWithoutAnnotation2() throws Exception {
TypeCheckResult ns =
parseAndTypeCheckWithScope("/** @type {!Object} */var t; t.x; t;");
Node n = ns.root;
Scope s = ns.scope;
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertEquals(type, OBJECT_TYPE);
assertTrue(type instanceof ObjectType);
ObjectType objectType = (ObjectType) type;
assertFalse(objectType.hasProperty("x"));
assertEquals(
Lists.newArrayList(OBJECT_TYPE),
registry.getTypesWithProperty("x"));
}
public void testFunctionMasksVariableBug() throws Exception {
testTypes("var x = 4; var f = function x(b) { return b ? 1 : x(true); };",
"function x masks variable (IE bug)");
}
public void testDfa1() throws Exception {
testTypes("var x = null;\n x = 1;\n /** @
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>x) { x.a = x.b; }";
assertEquals(50.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent4() throws Exception {
String js = "var n = {};\n /** @constructor */ n.T = function() {};\n" +
"/** @type n.T */ var x = new n.T();";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent5() throws Exception {
String js = "/** @enum {number} */ keys = {A: 1,B: 2,C: 3};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent6() throws Exception {
String js = "a = {TRUE: 1, FALSE: 0};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
private double getTypedPercent(String js) throws Exception {
Node n = compiler.parseTestCode(js);
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, n);
externAndJsRoot.setIsSyntheticBlock(true);
TypeCheck t = makeTypeCheck();
t.processForTesting(null, n);
return t.getTypedPercent();
}
private ObjectType getInstanceType(Node js1Node) {
JSType type = js1Node.getFirstChild().getJSType();
assertNotNull(type);
assertTrue(type instanceof FunctionType);
FunctionType functionType = (FunctionType) type;
assertTrue(functionType.isConstructor());
return functionType.getInstanceType();
}
public void testPrototypePropertyReference() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(""
+ "/** @constructor */\n"
+ "function Foo() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.bar = function(a){};\n"
+ "/** @param {Foo} f */\n"
+ "function baz(f) {\n"
+ " Foo.prototype.bar.call(f, 3);\n"
+ "}");
assertEquals(0, compiler.getErrorCount());
assertEquals(0, compiler.getWarningCount());
assertTrue(p.scope.getVar("Foo").getType() instanceof FunctionType);
FunctionType fooType = (FunctionType) p.scope.getVar("Foo").getType();
assertEquals("function (this:Foo, number): undefined",
fooType.getPrototype().getPropertyType("bar").toString());
}
public void testResolvingNamedTypes() throws Exception {
String js = ""
+ "/** @constructor */\n"
+ "var Foo = function() {}\n"
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Foo.prototype} */ ({bar: 1}));" +
"alert((new Foo()).bar);");
}
public void testLends7() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends {Foo.prototype|Foo} */ ({bar: 1}));",
"Bad type annotation. expected closing }");
}
public void testLends8() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @type {number} */ var Foo = 3;" +
"extend(Foo, /** @lends {Foo} */ ({bar: 1}));",
"May only lend properties to object types. Foo has type number.");
}
public void testLends9() throws Exception {
testClosureTypesMultipleWarnings(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends {!Foo} */ ({bar: 1}));",
Lists.newArrayList(
"Bad type annotation. expected closing }",
"Bad type annotation. missing object name in @lends tag"));
}
public void testDeclaredNativeTypeEquality() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ function Object() {};");
assertEquals(registry.getNativeType(JSTypeNative.OBJECT_FUNCTION_TYPE),
n.getFirstChild().getJSType());
}
public void testUndefinedVar() throws Exception {
Node n = parseAndTypeCheck("var undefined;");
assertEquals(registry.getNativeType(JSTypeNative.VOID_TYPE),
n.getFirstChild().getFirstChild().getJSType());
}
public void testFlowScopeBug1() throws Exception {
Node n = parseAndTypeCheck("/** @param {number} a \n"
+ "* @param {number} b */\n"
+ "function f(a, b) {\n"
+ "/** @type number */"
+ "var i = 0;"
+ "for (; (i + a) < b; ++i) {}}");
// check the type of the add node for i + f
assertEquals(registry.getNativeType(JSTypeNative.NUMBER_TYPE),
n.getFirstChild().getLastChild().getLastChild().getFirstChild()
.getNext().getFirstChild().getJSType());
}
public void testFlowScopeBug2() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ function Foo() {};\n"
+ "Foo.prototype.hi = false;"
+ "function foo(a, b) {\n"
+ " /** @type Array */"
+ " var arr;"
+ " /** @type number */"
+ " var iter;"
+ " for (iter =
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> 0; iter < arr.length; ++ iter) {"
+ " /** @type Foo */"
+ " var afoo = arr[iter];"
+ " afoo;"
+ " }"
+ "}");
// check the type of afoo when referenced
assertEquals(registry.createNullableType(registry.getType("Foo")),
n.getLastChild().getLastChild().getLastChild().getLastChild()
.getLastChild().getLastChild().getJSType());
}
public void testAddSingletonGetter() {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {};\n" +
"goog.addSingletonGetter(Foo);");
ObjectType o = (ObjectType) n.getFirstChild().getJSType();
assertEquals("function (): Foo",
o.getPropertyType("getInstance").toString());
assertEquals("Foo", o.getPropertyType("instance_").toString());
}
public void testTypeCheckStandaloneAST() throws Exception {
Node n = compiler.parseTestCode("function Foo() { }");
typeCheck(n);
TypedScopeCreator scopeCreator = new TypedScopeCreator(compiler);
Scope topScope = scopeCreator.createScope(n, null);
Node second = compiler.parseTestCode("new Foo");
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, second);
externAndJsRoot.setIsSyntheticBlock(true);
new TypeCheck(
compiler,
new SemanticReverseAbstractInterpreter(
compiler.getCodingConvention(), registry),
registry, topScope, scopeCreator, CheckLevel.WARNING, CheckLevel.OFF)
.process(null, second);
assertEquals(1, compiler.getWarningCount());
assertEquals("cannot instantiate non-constructor",
compiler.getWarnings()[0].description);
}
public void testUpdateParameterTypeOnClosure() throws Exception {
testTypes(
"/**\n" +
"* @constructor\n" +
"* @param {*=} opt_value\n" +
"* @return {?}\n" +
"*/\n" +
"function Object(opt_value) {}\n" +
"/**\n" +
"* @constructor\n" +
"* @param {...*} var_args\n" +
"*/\n" +
"function Function(var_args) {}\n" +
"/**\n" +
"* @type {Function}\n" +
"*/\n" +
// The line below sets JSDocInfo on Object so that the type of the
// argument to function f has JSDoc through its prototype chain.
"Object.prototype.constructor = function() {};\n",
"/**\n" +
"* @param {function(): boolean} fn\n" +
"*/\n" +
"function f(fn) {}\n" +
"f(function(g
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> String js, List<String> descriptions) throws Exception {
Node n = compiler.parseTestCode(js);
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, n);
externAndJsRoot.setIsSyntheticBlock(true);
assertEquals("parsing error: " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
// For processing goog.addDependency for forward typedefs.
new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, true)
.process(null, n);
CodingConvention convention = compiler.getCodingConvention();
new TypeCheck(compiler,
new ClosureReverseAbstractInterpreter(
convention, registry).append(
new SemanticReverseAbstractInterpreter(
convention, registry))
.getFirst(),
registry)
.processForTesting(null, n);
assertEquals(
"unexpected error(s) : " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
if (descriptions == null) {
assertEquals(
"unexpected warning(s) : " +
Joiner.on(", ").join(compiler.getWarnings()),
0, compiler.getWarningCount());
} else {
assertEquals(
"unexpected warning(s) : " +
Joiner.on(", ").join(compiler.getWarnings()),
descriptions.size(), compiler.getWarningCount());
for (int i = 0; i < descriptions.size(); i++) {
assertEquals(descriptions.get(i),
compiler.getWarnings()[i].description);
}
}
}
void testTypes(String js, String description, boolean isError)
throws Exception {
testTypes(DEFAULT_EXTERNS, js, description, isError);
}
void testTypes(String externs, String js, String description, boolean isError)
throws Exception {
Node n = parseAndTypeCheck(externs, js);
JSError[] errors = compiler.getErrors();
if (description != null && isError) {
assertTrue("expected an error", errors.length > 0);
assertEquals(description, errors[0].description);
errors = Arrays.asList(errors).subList(1, errors.length).toArray(
new JSError[errors.length - 1]);
}
if (errors.length > 0) {
fail("unexpected error(s):\n" + Joiner.on("\n").join(errors));
}
JSError[] warnings = compiler.getWarnings();
if (description != null && !isError) {
assertTrue("expected a warning", warnings.length > 0);
assertEquals(description, warnings[0].description);
warnings = Arrays.asList(warnings).subList(1, warnings.length).toArray(
new JSError[warnings.length - 1]);
}
if (warnings.length > 0
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>) {
fail("unexpected warnings(s):\n" + Joiner.on("\n").join(warnings));
}
}
/**
* Parses and type checks the JavaScript code.
*/
private Node parseAndTypeCheck(String js) {
return parseAndTypeCheck(DEFAULT_EXTERNS, js);
}
private Node parseAndTypeCheck(String externs, String js) {
return parseAndTypeCheckWithScope(externs, js).root;
}
/**
* Parses and type checks the JavaScript code and returns the Scope used
* whilst type checking.
*/
private TypeCheckResult parseAndTypeCheckWithScope(String js) {
return parseAndTypeCheckWithScope(DEFAULT_EXTERNS, js);
}
private TypeCheckResult parseAndTypeCheckWithScope(
String externs, String js) {
compiler.init(
Lists.newArrayList(JSSourceFile.fromCode("[externs]", externs)),
Lists.newArrayList(JSSourceFile.fromCode("[testcode]", js)),
compiler.getOptions());
Node n = compiler.getInput(new InputId("[testcode]")).getAstRoot(compiler);
Node externsNode = compiler.getInput(new InputId("[externs]"))
.getAstRoot(compiler);
Node externAndJsRoot = new Node(Token.BLOCK, externsNode, n);
externAndJsRoot.setIsSyntheticBlock(true);
assertEquals("parsing error: " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
Scope s = makeTypeCheck().processForTesting(externsNode, n);
return new TypeCheckResult(n, s);
}
private Node typeCheck(Node n) {
Node externsNode = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externsNode, n);
externAndJsRoot.setIsSyntheticBlock(true);
makeTypeCheck().processForTesting(null, n);
return n;
}
private TypeCheck makeTypeCheck() {
return new TypeCheck(
compiler,
new SemanticReverseAbstractInterpreter(
compiler.getCodingConvention(), registry),
registry,
reportMissingOverrides,
CheckLevel.OFF);
}
void testTypes(String js, String[] warnings) throws Exception {
Node n = compiler.parseTestCode(js);
assertEquals(0, compiler.getErrorCount());
Node externsNode = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externsNode, n);
makeTypeCheck().processForTesting(null, n);
assertEquals(0, compiler.getErrorCount());
if (warnings != null) {
assertEquals(warnings.length, compiler.getWarningCount());
JSError[] messages = compiler.getWarnings();
for (int i = 0; i < warnings.length && i < compiler
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>Position.put(null, ++astPositionCounter); // the implicit return is last.
// Now, generate the priority of nodes by doing a depth-first
// search on the CFG.
priorityCounter = 0;
DiGraphNode<Node, Branch> entry = cfg.getEntry();
prioritizeFromEntryNode(entry);
if (shouldTraverseFunctions) {
// If we're traversing inner functions, we need to rank the
// priority of them too.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
Node value = candidate.getValue();
if (value != null && value.isFunction()) {
Preconditions.checkState(
!nodePriorities.containsKey(candidate) || candidate == entry);
prioritizeFromEntryNode(candidate);
}
}
}
// At this point, all reachable nodes have been given a priority, but
// unreachable nodes have not been given a priority. Put them last.
// Presumably, it doesn't really matter what priority they get, since
// this shouldn't happen in real code.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
if (!nodePriorities.containsKey(candidate)) {
nodePriorities.put(candidate, ++priorityCounter);
}
}
// Again, the implicit return node is always last.
nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter);
}
/**
* Given an entry node, find all the nodes reachable from that node
* and prioritize them.
*/
private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) {
PriorityQueue<DiGraphNode<Node, Branch>> worklist =
new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator);
worklist.add(entry);
while (!worklist.isEmpty()) {
DiGraphNode<Node, Branch> current = worklist.remove();
if (nodePriorities.containsKey(current)) {
continue;
}
nodePriorities.put(current, ++priorityCounter);
List<DiGraphNode<Node, Branch>> successors =
cfg.getDirectedSuccNodes(current);
for (DiGraphNode<Node, Branch> candidate : successors) {
worklist.add(candidate);
}
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
astPosition.put(n, astPositionCounter++);
switch (n.getType()) {
case Token.FUNCTION:
if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) {
exceptionHandler.push(n);
return true;
}
return false;
case Token.TRY:
exceptionHandler.push(n);
return true;
}
/*
* We are going to stop the traversal depending on what the node's
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> parent
* is.
*
* We are only interested in adding edges between nodes that change control
* flow. The most obvious ones are loops and IF-ELSE's. A statement
* transfers control to its next sibling.
*
* In case of an expression tree, there is no control flow within the tree
* even when there are short circuited operators and conditionals. When we
* are doing data flow analysis, we will simply synthesize lattices up the
* expression tree by finding the meet at each expression node.
*
* For example: within a Token.SWITCH, the expression in question does not
* change the control flow and need not to be considered.
*/
if (parent != null) {
switch (parent.getType()) {
case Token.FOR:
// Only traverse the body of the for loop.
return n == parent.getLastChild();
// Skip the conditions.
case Token.IF:
case Token.WHILE:
case Token.WITH:
return n != parent.getFirstChild();
case Token.DO:
return n != parent.getFirstChild().getNext();
// Only traverse the body of the cases
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT_CASE:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handleExpr(n);
return;
case Token.THROW:
handleThrow(n);
return;
case Token.TRY:
handleTry(n);
return;
case Token.CATCH:
handleCatch(n);
return;
case Token.BREAK:
handleBreak(n);
return;
case Token.CONTINUE:
handleContinue(n);
return;
case Token.RETURN:
handleReturn(n);
return;
case Token.WITH:
handleWith(n);
return;
case Token.LABEL:
return;
default:
handleStmt(n);
return;
}
}
private void handleIf(Node node) {
Node thenBlock = node.getFirstChild().getNext();
Node elseBlock = thenBlock.getNext();
createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock));
if (elseBlock == null) {
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this)); // not taken branch
} else {
createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock));
}
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleWhile(Node node) {
// Control goes to the first statement if the condition evaluates to true.
createEdge(node, Branch.ON_TRUE,
computeFallThrough(node.getFirstChild().getNext()));
// Control goes to the follow() if the condition evaluates to false.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleDo(Node node) {
// The first edge can be the initial iteration as well as the iterations
// after.
createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild()));
// The edge that leaves the do loop if the condition fails.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleFor(Node forNode) {
if (forNode.getChildCount() == 4) {
// We have for (init; cond; iter) { body }
Node init = forNode.getFirstChild();
Node cond = init.getNext();
Node iter = cond.getNext();
Node body = iter.getNext();
// After initialization, we transfer to the FOR which is in charge of
// checking the condition (for the first time).
createEdge(init, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, compute
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>getNext();
}
if (nextSibling != null) {
return computeFallThrough(nextSibling);
} else {
// If there are no more siblings, control is transfered up the AST.
return computeFollowNode(fromNode, parent, cfa);
}
}
/**
* Computes the destination node of n when we want to fallthough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n;
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (handler.isFunction()) {
return;
}
Preconditions.checkState(handler.isTry());
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.isLabel()) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n) {
for (Node cur = n;
cur.getType() != Token.SCRIPT && cur.getType() != Token.FUNCTION;
cur = cur.getParent()) {
Node catchNode = getCatchHandlerForBlock(cur);
if (catchNode != null) {
return catchNode;
}
}
return null;
}
/**
* Locate the catch BLOCK given the first block in a TRY.
* @return The CATCH node or null there is no catch handler.
*/
static Node getCatchHandlerForBlock(Node block) {
if (block.isBlock() &&
block.getParent().isTry() &&
block.getParent().getFirstChild() == block) {
for (Node s = block.getNext(); s != null; s = s.getNext()) {
if (NodeUtil.hasCatchHandler(s)) {
return s.getFirstChild();
}
}
}
return null;
}
/**
* A {@link ControlFlowGraph} which provides a node comparator based on the
* pre-order traversal of the AST.
*/
private static class AstControlFlowGraph extends ControlFlowGraph<Node> {
private final Map<DiGraphNode<Node, Branch>, Integer> priorities;
/**
* Constructor.
* @param entry The entry node.
* @param priorities The map from nodes to position in the AST (to be
* filled by the {@link ControlFlowAnalysis#shouldTraverse}).
*/
private AstControlFlowGraph(Node entry,
Map<DiGraphNode<Node, Branch>, Integer> priorities,
boolean edgeAnnotations) {
super(entry,
true /* node annotations */, edgeAnnotations);
this.priorities = priorities;
}
@Override
/**
* Returns a node comparator based on the pre-order traversal of the AST.
* @param isForward x 'before' y in the pre-order traversal implies
* x 'less than' y (if true) and x 'greater than' y (if false).
*/
public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator(
boolean isForward) {
if (isForward) {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n1) - getPosition(n2);
}
};
} else {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n2) - getPosition(n1);
}
};
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> }
/**
* Gets the pre-order traversal position of the given node.
* @return An arbitrary counter used for comparing positions.
*/
private int getPosition(DiGraphNode<Node, Branch> n) {
Integer priority = priorities.get(n);
Preconditions.checkNotNull(priority);
return priority;
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> RecordProperty> properties) {
return new RecordType(this, properties);
}
/**
* Create an object type.
*/
public ObjectType createObjectType(String name, Node n,
ObjectType implicitPrototype) {
return new PrototypeObjectType(this, name, implicitPrototype);
}
/**
* Create an anonymous object type.
*/
public ObjectType createAnonymousObjectType() {
PrototypeObjectType type =
new PrototypeObjectType(this, null, null);
type.setPrettyPrint(true);
return type;
}
/**
* Set the implicit prototype if it's possible to do so.
* @return True if we were able to set the implicit prototype successfully,
* false if it was not possible to do so for some reason. There are
* a few different reasons why this could fail: for example, numbers
* can't be implicit prototypes, and we don't want to change the implicit
* prototype if other classes have already subclassed this one.
*/
public boolean resetImplicitPrototype(
JSType type, ObjectType newImplicitProto) {
if (type instanceof PrototypeObjectType) {
PrototypeObjectType poType = (PrototypeObjectType) type;
poType.clearCachedValues();
poType.setImplicitPrototype(newImplicitProto);
return true;
}
return false;
}
/**
* Create an anonymous object type for a native type.
*/
ObjectType createNativeAnonymousObjectType() {
PrototypeObjectType type =
new PrototypeObjectType(this, null, null, true);
type.setPrettyPrint(true);
return type;
}
/**
* Creates a constructor function type.
* @param name the function's name or {@code null} to indicate that the
* function is anonymous.
* @param source the node defining this function. Its type
* ({@link Node#getType()}) must be {@link Token#FUNCTION}.
* @param parameters the function's parameters or {@code null}
* to indicate that the parameter types are unknown.
* @param returnType the function's return type or {@code null} to indicate
* that the return type is unknown.
*/
public FunctionType createConstructorType(String name, Node source,
Node parameters, JSType returnType) {
return new FunctionType(this, name, source,
createArrowType(parameters, returnType), null,
null, true, false);
}
/**
* Creates an interface function type.
* @param name the function's name
* @param source the node defining this function. Its type
* ({@link Node#getType()}) must be {@link Token#FUNCTION}.
*/
public FunctionType createInterfaceType(String name, Node source) {
return FunctionType.forInterface(this, name, source);
}
/**
* Creates a parameterized type.
*/
public ParameterizedType createParameterizedType(
ObjectType objectType, JSType parameterType)
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> {
return new ParameterizedType(this, objectType, parameterType);
}
/**
* Creates a named type.
*/
@VisibleForTesting
public JSType createNamedType(String reference,
String sourceName, int lineno, int charno) {
return new NamedType(this, reference, sourceName, lineno, charno);
}
/**
* Identifies the name of a typedef or enum before we actually declare it.
*/
public void identifyNonNullableName(String name) {
Preconditions.checkNotNull(name);
nonNullableTypeNames.add(name);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) {
// If the type expression doesn't contain any names, just
// resolve it anyway.
boolean hasNames = hasTypeName(n);
if (hasNames) {
return new UnresolvedTypeExpression(this, n, sourceName);
}
}
return createFromTypeNodesInternal(n, sourceName, scope);
}
private boolean hasTypeName(Node n) {
if (n.getType() == Token.STRING) {
return true;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (hasTypeName(child)) {
return true;
}
}
return false;
}
/** @see #createFromTypeNodes(Node, String, StaticScope) */
private JSType createFromTypeNodesInternal(Node n, String sourceName,
StaticScope<JSType> scope) {
switch (n.getType()) {
case Token.LC: // Record type.
return createRecordTypeFromNodes(
n.getFirstChild(), sourceName, scope);
case Token.BANG: // Not nullable
return createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope)
.restrictByNotNullOrUndefined();
case Token.QMARK: // Nullable or unknown
Node firstChild = n.getFirstChild();
if (firstChild == null) {
return getNativeType(UNKNOWN_TYPE);
}
return createDefaultObjectUnion(
createFromTypeNodesInternal(
firstChild, sourceName, scope));
case Token.EQUALS: // Optional
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.ELLIPSIS: // Var args
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.STAR: // The AllType
return getNativeType(ALL_TYPE);
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
case Token.LB: // Array type
// TODO(nicksantos): Enforce membership restrictions on the Array.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
if (typeList != null &&
("Array".equals(n.getString()) ||
"Object".equals(n.getString()))) {
JSType parameterType =
createFromTypeNodesInternal(
typeList.getLastChild(), sourceName, scope);
namedType = new ParameterizedType(
this, (ObjectType) namedType, parameterType);
if (typeList.hasMoreThanOneChild()) {
JSType indexType =
createFromTypeNodesInternal(
typeList.getFirstChild(), sourceName, scope);
namedType = new IndexedType(
this, (ObjectType) namedType, indexType);
}
}
return createDefaultObjectUnion(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
boolean isConstructor = false;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS ||
current.getType() == Token.NEW) {
Node contextNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodesInternal(
contextNode, sourceName, scope)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
ScriptRuntime.getMessage0(
current.getType() == Token.THIS ?
"msg.jsdoc.function.thisnotobject" :
"msg.jsdoc.function.newnotobject"),
sourceName,
contextNode.getLineno(), contextNode.getCharno());
}
isConstructor = current.getType() == Token.NEW;
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.PARAM
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>_LIST) {
Node args = current.getFirstChild();
for (Node arg = current.getFirstChild(); arg != null;
arg = arg.getNext()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodesInternal(
arg.getFirstChild(), sourceName, scope));
}
} else {
JSType type = createFromTypeNodesInternal(
arg, sourceName, scope);
if (arg.getType() == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.setIsConstructor(isConstructor)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (hasType) {
// We have a declared type.
fieldType = createFromTypeNodesInternal(
fieldTypeNode.getLastChild(), sourceName, scope);
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
} else {
// Otherwise, the type is UNKNOWN.
fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
// Add the property to the record.
if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) {
// Duplicate field name, warning and skip
reporter.warning(
"Duplicate record field " + fieldName,
sourceName,
n.getLineno(), fieldNameNode.getCharno());
}
}
return builder.build();
}
/**
* Sets the template type name.
*/
public void setTemplateTypeName(String name) {
templateTypeName = name;
templateType = new TemplateType(this, name);
}
/**
* Clears the template type name.
*/
public void clearTemplateTypeName() {
templateTypeName = null;
templateType = null;
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>(N srcValue, Branch edgeValue) {
super.connect(srcValue, edgeValue, null);
}
/**
* Gets a comparator for the nodes. The default implementation returns
* {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}.
* @param isForward Whether the comparator sorts the nodes in the direction of
* the flow.
* @return a comparator or null (in particular, if not overriden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/** Exception related. */
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that is also represented by another control flow graph
* node.
*
* <p>For example, traversing an IF node as root will visit the two subtree
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edge.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// is bleed into the local scope and parameters has been assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body represent by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// Theses control structure is represented by its node that holds the
// condition. Each of them is a branch node based on its
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS> condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structure in that
// it has a initialization and a increment statement. Those
// two statements have its corresponding CFG nodes to represent them.
// The FOR node represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
if (NodeUtil.isForIn(parent)) {
return n == parent.getLastChild();
} else {
return NodeUtil.getConditionExpression(parent) != n;
}
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>DeclaredProperty("length", numberType, null);
}
private static void addMethod(
JSTypeRegistry registry, ObjectType receivingType, String methodName,
JSType returnType) {
receivingType.defineDeclaredProperty(methodName,
new FunctionBuilder(registry).withReturnType(returnType).build(),
null);
}
protected JSType createUnionType(JSType... variants) {
return registry.createUnionType(variants);
}
protected RecordTypeBuilder createRecordTypeBuilder() {
return new RecordTypeBuilder(registry);
}
protected JSType createNullableType(JSType type) {
return registry.createNullableType(type);
}
protected JSType createOptionalType(JSType type) {
return registry.createOptionalType(type);
}
/**
* Asserts that a Node representing a type expression resolves to the
* correct {@code JSType}.
*/
protected void assertTypeEquals(JSType expected, Node actual) {
assertTypeEquals(expected, new JSTypeExpression(actual, ""));
}
/**
* Asserts that a a type expression resolves to the correct {@code JSType}.
*/
protected void assertTypeEquals(JSType expected, JSTypeExpression actual) {
assertEquals(expected, resolve(actual));
}
/**
* Resolves a type expression, expecting the given warnings.
*/
protected JSType resolve(JSTypeExpression n, String... warnings) {
errorReporter.setWarnings(warnings);
return n.evaluate(null, registry);
}
/**
* A definition of all extern types. This should be kept in sync with
* javascript/externs/es3.js. This is used to check that the builtin types
* declared in {@link JSTypeRegistry} have the same type as that in the
* externs. It can also be used for any tests that want to use builtin types
* in their externs.
*/
public static final String ALL_NATIVE_EXTERN_TYPES =
"/**\n"
+ " * @constructor\n"
+ " * @param {*} opt_value\n"
+ " */\n"
+ "function Object(opt_value) {}\n"
+ "\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @extends {Object}\n"
+ " * @param {*} var_args\n"
+ " */\n"
+ "\n"
+ "function Function(var_args) {}\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @extends {Object}\n"
+ " * @param {*} var_args\n"
+ " * @return {!Array}\n"
+ " */\n"
+ "function Array(var_args) {}\n"
+ "\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @param {*}
Closure, 48
<FILEB>
<CHANGES>
if (inferred && rhsValue!= null && rhsValue.isFunction()) {
<CHANGEE>
<CHANGES>
if (info!= null) {
inferred = false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
inferred = false;
}
<CHANGEE>
<FILEE>
<FILEB>
//
// In a dynamic language with first-class functions, it's very difficult
// to know which one the user intended without looking at lots of
// contextual information (the second example demonstrates a small case
// of this, but there are some really pathological cases as well).
//
// The current algorithm checks if either the declaration has
// jsdoc type information, or @const with a known type,
// or a function literal with a name we haven't seen before.
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
<CHANGES>
if (inferred) {
<CHANGEE>
// Determining declaration for #2
<CHANGES>
inferred = !(rhsValue != null &&
rhsValue.isFunction() &&
(info != null || !scope.isDeclared(qName, false)));
<CHANGEE>
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
<FILEE>
<SCANS>
}
this.call = arrowType;
this.templateTypeName = templateTypeName;
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE));
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null);
this.kind = Kind.INTERFACE;
this.typeOfThis = new InstanceObjectType(registry, this);
}
/** Creates an instance for a function that is an interface. */
static FunctionType forInterface(
JSTypeRegistry registry, String name, Node source) {
return new FunctionType(registry, name, source);
}
@Override
public boolean isInstanceType() {
// The universal constructor is its own instance, bizarrely.
return isEquivalentTo(registry.getNativeType(U2U_CONSTRUCTOR_TYPE));
}
@Override
public boolean isConstructor() {
return kind == Kind.CONSTRUCTOR;
}
@Override
public boolean isInterface() {
return kind == Kind.INTERFACE;
}
@Override
public boolean isOrdinaryFunction() {
return kind == Kind.ORDINARY;
}
@Override
public FunctionType toMaybeFunctionType() {
return this;
}
@Override
public boolean canBeCalled() {
return true;
}
public boolean hasImplementedInterfaces() {
if (!implementedInterfaces.isEmpty()){
return true;
}
FunctionType superCtor = isConstructor() ?
getSuperClassConstructor() : null;
if (superCtor != null) {
return superCtor.hasImplementedInterfaces();
}
return false;
}
public Iterable<Node> getParameters() {
Node n = getParametersNode();
if (n != null) {
return n.children();
} else {
return Collections.emptySet();
}
}
/** Gets an LP node that contains all params. May be null. */
public Node getParametersNode() {
return call.parameters;
}
/** Gets the minimum number of arguments that this function requires. */
public int getMinArguments() {
// NOTE(nicksantos): There are some native functions that have optional
// parameters before required parameters. This algorithm finds the position
// of the last required parameter.
int i = 0;
int min = 0;
for (Node n : getParameters()) {
i++;
if (!n.isOptionalArg() && !n.isVarArgs()) {
min = i;
}
}
return min;
}
/**
* Gets the maximum number of arguments that this function